【问题标题】:Testing Observables -- check for side effects on subscribe测试 Observables——检查订阅的副作用
【发布时间】:2019-01-26 05:34:32
【问题描述】:

我想从消费者的角度测试 observable 的行为方式。

我不知道当我订阅(冷)或不订阅(热)时是否会有副作用。

有没有办法在单元测试中验证这种行为?

我已经连接了来自 rxjs/testing 的 TestScheduler,但我没有看到一个很好的方法来验证 observable 的创建次数。

// ...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 次用于我的订阅,1 次来自中间件)。

我想在更改 observable 的行为以匹配我想要的 api.create 之前编写一个失败的测试(真否定)。

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

我试过了:

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

【问题讨论】:

    标签: unit-testing rxjs


    【解决方案1】:

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

    一次。

    const create$ = api.create(potato)
    

    这会创建你的 observable。您对 observable 的 .pipe 附件是从 observable 点到订阅者的数据路径的一部分。

    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!
    

    【讨论】:

    • 太棒了!是的,正如你所说,我想做一个 share()。或者是 publishReplay()、refCount()。但首先,我想“编写失败的测试”来验证我的用例的冷可观察行为。
    • 如果您想到可能发生的副作用,您应该在 subscribe 之后并在通过可观察数据路径提供值之前立即对其进行测试。
    【解决方案2】:

    这是迄今为止我发现的在单元测试中验证每个订阅只调用一次内部 observable 的最佳方法。

    1. 创建一个与实际操作类似的假冷可观察对象
    2. 使用 spyOn 从“做真正的工作”函数返回该值
    3. 调用外部api
    4. 验证伪造的cold observable上的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');
    });
    

    【讨论】:

      猜你喜欢
      • 2020-02-28
      • 1970-01-01
      • 1970-01-01
      • 2016-10-09
      • 2022-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-13
      相关资源
      最近更新 更多