如何测试 RxJs 去抖 epics?

how to test RxJs debounced epics?

我正在尝试为 redux-observable 史诗编写测试函数。史诗在应用程序中运行良好,我可以检查它是否正确地消除了动作并在发射前等待 300 毫秒。但是出于某种原因,当我试图开玩笑地测试它时,去抖运算符会立即触发。所以我确保去抖动正常工作的测试用例失败了。

这是测试用例

it('shall not invoke the movies service neither dispatch stuff if the we invoke before 300ms', done => {
    const $action = ActionsObservable.of(moviesActions.loadMovies('rambo'));

    loadMoviesEpic($action).subscribe(actual => {
        throw new Error('Should not have been invoked');
    });

    setTimeout(() => {
        expect(spy).toHaveBeenCalledTimes(0);
        done();
    }, 200);

});

这是我的间谍定义。

jest.mock('services/moviesService');

const spy = jest.spyOn(moviesService, 'searchMovies');

beforeEach(() => {
    moviesService.searchMovies.mockImplementation(keyword => {
        return Promise.resolve(moviesResult);
    });
    spy.mockClear();
});

这就是史诗

import { Observable, interval } from 'rxjs';
import { combineEpics } from 'redux-observable';

import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounce';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/from';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/interval';
import 'rxjs/add/observable/concat';

import actionTypes from 'actions/actionTypes';
import * as moviesService from 'services/moviesService';
import * as moviesActions from 'actions/moviesActions';

const DEBOUNCE_INTERVAL_IN_MS = 300;
const MIN_MOVIES_SEARCH_LENGTH = 3;

export function loadMoviesEpic($action) {
  return $action
    .ofType(actionTypes.MOVIES.LOAD_MOVIES)
    .debounce(() => Observable.interval(DEBOUNCE_INTERVAL_IN_MS))
    .filter(({ payload }) => payload.length >= MIN_MOVIES_SEARCH_LENGTH)
    .switchMap(({ payload }) => {
      const loadingAction = Observable.of(moviesActions.loadingMovies());

      const moviesResultAction = Observable.from(
        moviesService.searchMovies(payload)
      )
        .map(moviesResultList => moviesActions.moviesLoaded(moviesResultList))
        .catch(err => Observable.of(moviesActions.loadError(err)));

      return Observable.concat(loadingAction, moviesResultAction);
    });
}

const rootEpic = combineEpics(loadMoviesEpic);

export default rootEpic;

所以基本上这个东西不应该被调用,因为去抖动时间是 300 毫秒,我试图在 200 毫秒后检查间谍。但在 10 毫秒后,间谍程序被调用。

如何正确测试这部史诗?我接受任何建议,但最好我想避免大理石测试,只依赖计时器和假计时器。

谢谢 :D

而不是 setTimeout 尝试 advanceTimersByTime

问题是 debouncedebounceTime 发出 立即 当它们到达 observable 的末尾时它们正在去抖动。

因此,由于您使用 of 进行发射,因此到达了 observable 的末尾并且 debounce 允许立即通过最后发射的值。

这是一个演示行为的简单测试:

import { of } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

test('of', () => {
  const spy = jest.fn();
  of(1, 2, 3)
    .pipe(debounceTime(1000000))
    .subscribe(v => spy(v));
  expect(spy).toHaveBeenCalledWith(3);  // 3 is emitted immediately
})

为了有效地测试 debouncedebounceTime,您需要使用一个持续发射并且不会结束的可观察对象,例如 interval:

import { interval } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

test('interval', done => {
  const spy = jest.fn();
  interval(100)
    .pipe(debounceTime(500))
    .subscribe(v => spy(v));
  setTimeout(() => {
    expect(spy).not.toHaveBeenCalled();  // Success!
    done();
  }, 1000);
});