action creator 没有 return value to stream in marble test

action creator does not return value to stream in marble test

我得到了 Epic,它在应用程序中运行良好,但我无法进行弹珠测试。我在 map 中调用 action creator,它确实 return 将对象更正到流中,但在测试中我得到的是空流。

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

actualreturns 空数组中的输出流 我尝试 return 从 action creator 的 map observable 中获取,因为 action expected object 导致应用程序崩溃。

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


以下是对上一版问题的回答。我保留它以供参考,因为我认为该内容对那些试图在史诗测试中模拟 promise-returning 函数的人很有用。

我认为你的问题是你史诗中的from(axios.post(...))。 Axios return 是一个承诺,而 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));
}

一旦有了这个包装器,就可以将该函数模拟为 return 常量 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 object。