Как выполнить модульное тестирование отмены запросов RXJS в полете
Я использую 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 ответ
Так что я немного погрузился в это. У меня есть две мысли:
- В модульном тесте мы действительно просто хотим протестировать код, который мы пишем. Если я использую стороннюю библиотеку, я бы сделал предположение, что она должным образом проверена модулем (скажем, посмотрев исходный код этой библиотеки). Модульные тесты для DataPersistence не тестируют на отмену прямо сейчас (потому что мы используем switchMap и делаем предположение, что его функциональность работает).
- Существует актуальная проблема при попытке проверить с
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
).
Надеюсь это поможет. Я думаю, что ваши тесты не должны были бы проверять функциональность базовой библиотеки (в этот момент это может быть не строгий модульный тест, а скорее интеграционный тест).