Как выполнить модульное тестирование отмены запросов RXJS в полете - PullRequest
0 голосов
/ 03 декабря 2018

Я использую NGRX и использую эффекты для отправки HTTP-запросов.Если пользователь отправляет другой запрос, любой предыдущий запрос должен быть отменен.Это работает нормально, когда я тестирую вручную, но я хочу иметь возможность тестировать этот модуль.Чтобы проверить это, я издеваюсь над сервисом, который отправляет HTTP-запрос и отправляет ответ после определенной задержки.Затем у меня есть горячая наблюдаемая Мраморная, которая вызывает 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;первоначальный запрос будет отменен, если поступит новое действие с тем же идентификатором.Это похоже на использование 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 голосов
/ 16 декабря 2018

Так что я немного углубился в это.У меня есть две мысли:

  1. В модульном тесте мы действительно просто хотим проверить код, который мы пишем.Если я использую стороннюю библиотеку, я бы предположил , что она должным образом проверена модулем (скажем, посмотрев исходный код этой библиотеки).Модульные тесты для DataPersistence не тестируют на отмену прямо сейчас (потому что мы используем switchMap и делаем предположение, что его функциональность работает).пример

В ваших тестах 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).

Надеюсь, это поможет.Я думаю, что ваши тесты не должны были бы проверять функциональность базовой библиотеки (в этот момент это может быть не строгий модульный тест, а скорее интеграционный тест).

...