测试Observables-检查订阅是否有副作用

时间:2019-01-25 18:41:50

标签: unit-testing rxjs

我想从消费者的角度测试可观察对象的行为。

当我订阅(冷)或不订阅(热)时,我无法确定是否会有副作用。

有没有办法可以在单元测试中验证此行为?

我从rxjs / test中获得了TestScheduler的信息,但是我没有看到一种很好的方法来验证可观察对象的创建次数。

// ...the create method has been mocked to emit after 3 frames
const create$ = api.create(potato).pipe(
  tap(console.log.bind(null, 'object created'))
);
create$.subscribe();
create$.subscribe();
create$.subscribe();

// Test how many times create$ has gotten a subscription, generated a cold observable, and completed.

const timing = '---(a|)'; // wait 3 frames, emit value of `a`, complete
const values = { a: potato };
expectObservable(create$).toBe(timing, values);

此测试通过,但是“创建对象”消息触发了四次(我的订阅发出3次,中间件发出一次)。

我想写一个失败的测试(真否定),然后再更改可观察对象的行为以使其符合api.create的要求。

如何验证创建行为仅执行一次?

我尝试过:

  • spyOn,但是实际的create方法仅被调用一次。
  • Array.isArray(create$.observers)-太间接了,只检查它是否很热,而不是它的行为是否符合预期。
  • tap(() => runCount++) \ expect(runCount).toBe(1)-如果我刷新调度程序,则可以使用,但是似乎超出了rxjs测试的标准。
  • 使用Observable.create和工厂功能来手动跟踪运行计数。也可以,有点冗长。

2 个答案:

答案 0 :(得分:0)

我不确定我是否遵循您的要求,但是我可以解决您对创建可观察项的次数的担忧:

一次。

const create$ = api.create(potato)

这将创建可观察的对象。从可观测对象到订阅者的.pipe附件是可观测对象的数据路径的一部分。

potato ---(pipe)--->.subscribe()
     +----(pipe)--->.subscribe()
     +----(pipe)--->.subscribe()
     +----(pipe)--->(expectObservable inspection)

相反,您似乎可能希望在其中添加一条额外的管道来共享结果。也许并不奇怪,该管道称为share

输入

import { Observable, Subject } from 'rxjs';
import { share, tap } from 'rxjs/operators';

let obj: Subject<string> = new Subject<string>();
let obs: Observable<string> = obj.pipe(tap(() => console.log('tap pipe')));

obs.subscribe((text) => console.log(`regular: ${text}`));
obs.subscribe((text) => console.log(`regular: ${text}`));
obs.subscribe((text) => console.log(`regular: ${text}`));

let shared: Observable<string> = obs.pipe(share());

shared.subscribe((text) => console.log(`shared: ${text}`));
shared.subscribe((text) => console.log(`shared: ${text}`));
shared.subscribe((text) => console.log(`shared: ${text}`));

obj.next('Hello, world!');

输出

tap pipe
regular: Hello, world!
tap pipe
regular: Hello, world!
tap pipe
regular: Hello, world!
tap pipe
shared: Hello, world!
shared: Hello, world!
shared: Hello, world!

答案 1 :(得分:0)

到目前为止,这是我迄今为止发现的最佳方法,可以在单元测试中对每个订阅仅一次调用一次内部可观察性。

  1. 创建一个类似于您的实际操作的假冷观测
  2. 使用spyOn从“执行真实工作”函数中返回该值
  3. 调用外部api
  4. 在可观察到的假冷情况下验证runCount
scheduler.run(rx => {
  let runCount = 0;
  const timing = '---(a|)';
  const values = { a: {x:42} };

  // This represents the inner cold observable.
  // We want to validate that it does/does not get called once per subscription
  const mockedCreate$ = rx.cold(timing, values).pipe(
    tap(() => runCount++),
  );
  spyOn(api, 'doCreate').and.returnValue(mockedCreate$);

  const create$ = api.create({x:42}); // internally, calls doCreate
  create$.subscribe();
  create$.subscribe();
  create$.subscribe();
        // Explanation:
        // If api.create wasn't multicasting/sharing the result of the doCreate
        // operation, we'd see multiple actual save operations, not just 1

  rx.expectObservable(create$).toBe(timing, values);
  scheduler.flush();
  expect(runCount).toBe(1, 'how often the "real" create operation ran');
});