动作创建者不会在大理石测试中返回值以进行流式处理

时间:2019-02-27 10:32:55

标签: javascript redux react-redux rxjs redux-observable

我遵循了Epic,该Epic在应用程序中效果很好,但是我无法进行大理石测试。我在map中调用动作创建者,它确实将正确的对象返回到流中,但是在测试中,我得到的是空流。

export const updateRemoteFieldEpic = action$ =>
  action$.pipe(
    ofType(UPDATE_REMOTE_FIELD),
    filter(({ payload: { update = true } }) => update),
    mergeMap(({ payload }) => {
      const { orderId, fields } = payload;
      const requiredFieldIds = [4, 12]; //  4 = Name, 12 = Client-lookup
      const requestData = {
        id: orderId,
        customFields: fields
          .map(field => {
            return (!field.value && !requiredFieldIds.includes(field.id)) ||
              field.value
              ? field
              : null;
          })
          .filter(Boolean)
      };

      if (requestData.customFields.length > 0) {
        return from(axios.post(`/customfields/${orderId}`, requestData)).pipe(
          map(() => queueAlert("Draft Saved")),
          catchError(err => {
            const errorMessage =
              err.response &&
              err.response.data &&
              err.response.data.validationResult
                ? err.response.data.validationResult[0]
                : undefined;
            return of(queueAlert(errorMessage));
          })
        );
      }
      return of();
    })
  );

在服务器成功响应后,我正在呼叫queueAlert动作创建者。

export const queueAlert = (
  message,
  position = {
    vertical: "bottom",
    horizontal: "center"
  }
) => ({
  type: QUEUE_ALERT,
  payload: {
    key: uniqueId(),
    open: true,
    message,
    position
  }
});

这是我的测试用例

describe("updateRemoteFieldEpic", () => {
  const sandbox = sinon.createSandbox();
  let scheduler;
  beforeEach(() => {
    scheduler = new TestScheduler((actual, expected) => {
      expect(actual).toEqual(expected);
    });
  });
  afterEach(() => {
    sandbox.restore();
  });
  it("should return success message", () => {
    scheduler.run(ts => {
      const inputM = "--a--";
      const outputM = "--b--";
      const values = {
        a: updateRemoteField({
          orderId: 1,
          fields: [{ value: "test string", id: 20 }],
          update: true
        }),
        b: queueAlert("Draft Saved")
      };

      const source = ActionsObservable.from(ts.cold(inputM, values));
      const actual = updateRemoteFieldEpic(source);

      const axiosStub = sandbox
        .stub(axios, "post")
        .returns([]);

      ts.expectObservable(actual).toBe(outputM, values);
      ts.flush();
      expect(axiosStub.called).toBe(true);
    });
  });
});
actual中的

输出流返回空数组 我试图从可观察到动作创建者的地图返回,该动作使应用程序崩溃,因为动作是对象。

1 个答案:

答案 0 :(得分:0)

通过将axios.post(...)存为[],您将在史诗中获得from([])-一个空的可观察对象,不发出任何值。这就是为什么您的mergeMap永远不会被调用的原因。您可以通过使用单元素数组作为存根值来解决此问题,例如[null][{}]


以下是该问题先前版本的答案。我保留它作为参考,因为我认为该内容对于那些试图在史诗般的测试中模拟承诺返回功能的人很有用。

我认为您的问题是史诗中的from(axios.post(...))。 Axios返回了一个Promise,而RxJS TestScheduler无法实现该同步,因此expectObservable将无法正常工作。

我通常解决这个问题的方法是创建一个简单的包装器模块,该模块执行Promise-to-Observable转换。在您的情况下,可能看起来像这样:

// api.js

import axios from 'axios';
import { map } from 'rxjs/operators';

export function post(path, data) {
  return from(axios.post(path, options));
}

一旦有了这个包装器,就可以模拟该函数以返回常量Observable,从而完全保证了承诺。如果使用Jest进行此操作,则可以直接模拟该模块:

import * as api from '../api.js';

jest.mock('../api.js');

// In the test:
api.post.mockReturnValue(of(/* the response */));

否则,您还可以使用redux-observable的dependency injection mechanism注入API模块。然后,您的史诗将其作为第三个参数:

export const updateRemoteFieldEpic = (action$, state, { api }) =>
  action$.pipe(
    ofType(UPDATE_REMOTE_FIELD),
    filter(({ payload: { update = true } }) => update),
    mergeMap(({ payload }) => {
       // ...
       return api.post(...).pipe(...);
    })
  );

在测试中,您只需传递一个经过模拟的api对象。