如何对飞行中RXJS请求的取消进行单元测试

时间:2018-12-03 03:05:21

标签: angular rxjs ngrx nrwl jasmine-marbles

我正在使用NGRX,并使用“效果”发送HTTP请求。如果用户发送另一个请求,则任何先前的请求都应被取消。当我手动测试时,它工作正常,但我希望能够对此进行单元测试。为了测试这一点,我模拟了发送HTTP请求并在一定延迟后发送响应的服务。然后,我有一个Marble hot observable触发了4个请求。我希望我的效果只会触发一次。但是,它根本不会被触发。

单元测试:

it('should only do one request at a time', fakeAsync(() => {
    // Arrange
    const data = createTestData();
    const dataServiceSpy = TestBed.get(DataService);
    dataServiceSpy.getData = jest.fn(
        (query: DataQuery) => {
            const waitTime = 1000 * + query.index;
            return of(assets).pipe(delay(waitTime));
        }
    );         
    // Act
    actions = hot('-abcd-|', {
        a: new SearchData({ index: '6' }),
        b: new SearchData({ index: '5' }),
        c: new SearchData({ index: '4' }),
        d: new SearchData({ index: '1' })
    });

    tick(10000);

    // Assert
    expect(effects.loadData$).toBeObservable(
        hot('-a-|', { a: new SearchDataComplete(assets) })
    );
}));

因此,我要发送4个搜索请求;第一个应在6秒后返回数据,第二个应在5秒后返回数据,依此类推。 但是,我的单元测试失败了,因为loadData $是空的可观察到的东西,但它预期只有一项。

如果我删除了间谍中的延迟,则它将按预期工作,并且loadData $包含4个结果。

My Effect正在使用NX DataPersistence,如果您提供id函数,它将负责取消操作;如果有新的动作具有相同的ID,它将取消初始请求。类似于使用this.actions $ .pipe(switchMap(...))

@Effect()
loadData$ = this.dataPersistence.fetch(ActionTypes.SearchData, {
    id:  (action, state) => {
        return action.type
    },

    run: (action, state) => {
        return this.dataService
            .searchData(action.payload)
            .pipe(                   
                map(data => new SearchDataComplete(data))
            );
    },

1 个答案:

答案 0 :(得分:1)

所以我对此进行了一些研究。我有两个想法:

  1. 在单元测试中,我们真的只想测试我们编写的代码。如果我使用的是第三方库,我会假设已正确进行了单元测试(例如,通过查看该库的源代码)。 DataPersistence的单元测试现在不测试取消(因为我们使用switchMap并假设其功能有效)。
  2. 在示例中尝试使用delay进行测试时存在一个实际问题

在您的测试中,tick会在订阅该效果之前触发(当您在其下方调用expect时)。

一种解决方法如下:

describe('loadTest$', () => {
    it('should only do one request at a time', () => {

      // create a synchronous scheduler (VirtualTime)
      const scheduler = new VirtualTimeScheduler();

      // Arrange
      const data = createTestData();
      const dataServiceSpy = TestBed.get(DataService);

      TestBed.configureTestingModule({
        imports: [NxModule.forRoot(), StoreModule.forRoot({})],
        providers: [
          TestEffects,
          provideMockActions(() => actions),
          {
            provide: DataService,
            useValue: {
              searchData: jest.fn(
                  (query: DataQuery) => {
                      const waitTime = 1000 * + query.index;
                      return of(assets).pipe(delay(waitTime, scheduler));
                  }
              )
            }
          }
        ]
      });

      actions = of(
          new SearchData({ index: '6' }),
          new SearchData({ index: '5' }),
          new SearchData({ index: '4' }),
          new SearchData({ index: '1' })
      );

      const res = [];
      effects.loadData$.subscribe(val => res.push(val));

      scheduler.flush(); // we flush the observable here

      expect(res).toEqual([{ a: new SearchDataComplete(assets) }]);
    });
});

我们可以使用同步调度程序并手动刷新它。通常,我们不需要执行任何操作。仅仅是因为我们需要delay(其他需要调度程序的运营商(例如debounceTime也会发生这种情况)。

希望这会有所帮助。我认为您的测试不需要测试基础库的功能(在那时,它可能不是严格的单元测试,而更多的是集成测试)。