【问题标题】:Redux-observable: failed jest test for epicRedux-observable:史诗的开玩笑测试失败
【发布时间】:2017-08-28 12:43:27
【问题描述】:

我按照documentation 的步骤来测试史诗。

...
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))
    })

【问题讨论】:

    标签: reactjs unit-testing jestjs redux-observable


    【解决方案1】:

    原因是承诺总是在 next microtask 上解析,因此您的 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 和中间件,而是直接使用我们自己的 mock 调用史诗函数。更容易和更清洁。

    通过这种方法,我们可以利用 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
        }]);
      });
    

    【讨论】:

    • 我看到解决方案是正确的。我得到“TypeError: Cannot read property '_location' of null”,尽管它不在问题范围内。
    • 我为此苦苦思索了几个小时...尝试了这个并得到“...toArray is not a function”错误。
    • 是的,始终由 RxJS 用户决定他们希望如何导入运算符。
    • 仍然有问题...... Observable.ajax 的史诗返回 DOMException InvalidAccessError。我正在使用 Jest 和 Nock。似乎它没有正确模拟......
    • 问题不在于ajax调用。任何异步执行的 observable 都会使其超时。对done 的调用永远不会发生。
    【解决方案2】:

    首先,使用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
          }
        ]);
      });
    });
    

    好好享受吧:)

    【讨论】:

      猜你喜欢
      • 2021-11-13
      • 2019-01-23
      • 2018-02-13
      • 1970-01-01
      • 2017-11-19
      • 2017-05-06
      • 2020-10-08
      • 1970-01-01
      • 2018-09-01
      相关资源
      最近更新 更多