Redux-observable:Epic的玩笑测试失败



我按照文档中的步骤测试了史诗。

...
store.dispatch({ type: FETCH_USER });
expect(store.getActions()).toEqual([
   { type: FETCH_USER },
   { type: FETCH_USER_FULFILLED, payload }
]);
...

但是我失败了,因为稍后收到了第二个操作,如下所示。

Test failed
    Expected value to equal:
      [{"type": "FETCH_USER"}, {"type": "FETCH_USER_FULFILLED", "payload": [some]}]
    Received:
      [{"type": "FETCH_USER"}]
    Difference:
    - Expected
    + Received
    @@ -1,20 +1,5 @@
     Array [
       Object {"type": "FETCH_USER"},
       Object {"type": "FETCH_USER_FULFILLED", "payload": [some]} ] // this is what should be.

所以我想我应该知道调度何时完成或类似的事情。我该如何解决这个问题?

我使用了fetch((和Rx.Observable.fromPromise而不是ajax.getJSON((

这是我的史诗。

const fetchUserEpic = (action$) =>
  action$
    .ofType(FETCH_USER)
    .mergeMap(() => {
      return Rx.Observable.fromPromise(api.fetchUser())
        .map((users) => ({
          type: FETCH_USER_FULFILLED,
          payload: { users }
        }))
        .catch((error) => Rx.Observable.of({
          type: FETCH_USER_ERROR,
          payload: { error }
        }))
        .takeUntil(action$.ofType(FETCH_USER_CANCELLED))
    })

原因是承诺总是在下一个微任务上解析,因此您的api.fetchUser()不会同步发出。

你需要模拟它,使用类似Promise.resolve().then(() => expect(store.getActions).toEqual(...)的东西等到下一个微任务,或者你可以尝试直接测试你的史诗而不使用redux。

it('Epics with the appropriate input and output of actions', (done) => {
  const action$ = ActionsObservable.of({ type: 'SOMETHING' });
  somethingEpic(action$, store)
    .toArray() // collects everything in an array until our epic completes
    .subscribe(actions => {
      expect(actions).to.deep.equal([
        { type: 'SOMETHING_FULFILLED' }// whatever actions
      ]);
      done();
    });
});

这将是我(或其他人(有时间编写文档时我们首选的文档测试故事。因此,我们无需在测试中使用 redux 和中间件,只需使用自己的模拟直接调用 epic 函数。更容易和更干净。

通过这种方法,我们可以利用 redux-observable 的新依赖注入功能:https://redux-observable.js.org/docs/recipes/InjectingDependenciesIntoEpics.html


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

// Notice the third argument is our injected dependencies!
const fetchUserEpic = (action$, store, { getJSON }) =>
  action$.ofType('FETCH_USER')
    .mergeMap(() =>
      getJSON(`/api/users/${payload}`)
        .map(response => ({
          type: 'FETCH_USER_FULFILLED',
          payload: response
        }))
    );

import { ActionsObservable } from 'redux-observable';
import { fetchUserEpic } from './somewhere/fetchUserEpic';
const mockResponse = { name: 'Bilbo Baggins' };
const action$ = ActionsObservable.of({ type: 'FETCH_USERS_REQUESTED' });
const store = null; // not needed for this epic
const dependencies = {
  getJSON: url => Observable.of(mockResponse)
};
// Adapt this example to your test framework and specific use cases
fetchUserEpic(action$, store, dependencies)
  .toArray() // buffers all emitted actions until your Epic naturally completes()
  .subscribe(actions => {
    assertDeepEqual(actions, [{
      type: 'FETCH_USER_FULFILLED',
      payload: mockResponse
    }]);
  });

首先,使用 isomorphic-fetch 而不是 Observable.ajax 来支持 nock,就像这样

const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
  .then(res => res.json());
  return Observable.from(request);
};

所以我的史诗是:

const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
  action$
    .ofType(FETCH_MODEL)
    .mergeMap((action: FetchModel) =>
      fetchDynamicData(action.url, action.params)
        .map((payload: FetchedData) => fetchModelSucc(payload.data))
        .catch(error => Observable.of(
          fetchModelFail(error)
      )));

然后,您可能需要一个间隔来决定何时完成测试。

describe("epics", () => {
  let store: MockStore<{}>;
  beforeEach(() => {
    store = mockStore();
  });
  afterEach(() => {
    nock.cleanAll();
    epicMiddleware.replaceEpic(epic);
  });
  it("fetch data model succ", () => {
    const payload = {
      code: 0,
      data: someData,
      header: {},
      msg: "ok"
    };
    const params = {
      data1: 100,
      data2: "4"
    };
    const mock = nock("https://test.com")
      .get("/test")
      .query(params)
      .reply(200, payload);
    const go = new Promise((resolve) => {
      store.dispatch({
        type: FETCH_MODEL,
        url: "https://test.com/test",
        params
      });
      let interval: number;
      interval = window.setInterval(() => {
        if (mock.isDone()) {
          clearInterval(interval);
          resolve(store.getActions());
        }
      }, 20);
    });
    return expect(go).resolves.toEqual([
      {
        type: FETCH_MODEL,
        url: "https://test.com/assignment",
        params
      },
      {
        type: FETCH_MODEL_SUCC,
        data: somData
      }
    ]);
  });
});

享受它:)

最新更新