Injecting Dependencies Into Epics

将依赖注入到你 Epics 中帮助测试。

假设你需要和网络交互。你可能会直接从 rxjs 中使用 ajax 工具:

  1. import { ajax } from 'rxjs/observable/dom/ajax';
  2. const fetchUserEpic = (action$, store) =>
  3. action$.ofType('FETCH_USER')
  4. .mergeMap(({ payload }) =>
  5. ajax.getJSON(`/api/users/${payload}`)
  6. .map(response => ({
  7. type: 'FETCH_USER_FULFILLED',
  8. payload: response
  9. }))
  10. );

但是这种方式有一个问题: 包含 epic 的文件直接导入了它的依赖,所以 mocking 就会非常困难。

一种方式可能是 mock window.XMLHttpRequest, 但是这工作量很大并且你不是在仅仅测试你的 Epic,你是在测试 RxJS 正确使用 XMLHttpRequest,事实上,这并不是你的测试目的。

注入依赖

你可以使用 createEpicMiddlewaredependencies 配置项来注入依赖:

  1. import { createEpicMiddleware, combineEpics } from 'redux-observable';
  2. import { ajax } from 'rxjs/observable/dom/ajax';
  3. import rootEpic from './somewhere';
  4. const epicMiddleware = createEpicMiddleware(rootEpic, {
  5. dependencies: { getJSON: ajax.getJSON }
  6. });

任何你提供的都会在创建 store 之后被当作所有 Epics 的第三个参数传入。

现在你的 Epic 可以使用注入的 getJSON,而不用导入:

  1. // 注意第三个参数是我们注入的依赖!
  2. const fetchUserEpic = (action$, store, { getJSON }) =>
  3. action$.ofType('FETCH_USER')
  4. .mergeMap(({ payload }) =>
  5. getJSON(`/api/users/${payload}`)
  6. .map(response => ({
  7. type: 'FETCH_USER_FULFILLED',
  8. payload: response
  9. }))
  10. );

为了测试,你可以直接调用 Epic,传递 mock 的 getJSON:

  1. import { ActionsObservable } from 'redux-observable';
  2. import { fetchUserEpic } from './somewhere/fetchUserEpic';
  3. const mockResponse = { name: 'Bilbo Baggins' };
  4. const action$ = ActionsObservable.of({ type: 'FETCH_USERS_REQUESTED' });
  5. const store = null; // 该 epic 不需要
  6. const dependencies = {
  7. getJSON: url => Observable.of(mockResponse)
  8. };
  9. // 将这个例子适用到你的测试框架和特定用例
  10. fetchUserEpic(action$, store, dependencies)
  11. .toArray() // 缓冲所有发出的 actions 直到 epic 自然完成
  12. .subscribe(actions => {
  13. assertDeepEqual(actions, [{
  14. type: 'FETCH_USER_FULFILLED',
  15. payload: mockResponse
  16. }]);
  17. });