You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Marble测试中Action Creator未向流返回值问题求助

解决Redux-Observable Marble测试中Epic输出流为空的问题

我仔细看了你的代码,问题主要出在axios的stub设置和测试执行顺序上,导致你的Epic在测试中没有正确发出预期的action。下面一步步拆解问题和解决方案:

核心问题分析

你在测试里给axios.post设置的stub返回了[]:

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

但from([])(也就是你Epic里的from(axios.post(...)))会创建一个立即完成但不发出任何值的Observable,所以后续的map(() => queueAlert("Draft Saved"))根本不会被执行,自然输出流就是空的。

另外,你设置stub的时机有点晚——在创建了Epic的actual流之后才设置,这可能导致Epic执行时用的还是原始的axios.post实现,而不是你的stub。

解决方案步骤

1. 让axios stub返回一个resolved的Promise

axios.post本身返回的是Promise,所以你的stub也应该返回一个resolved的Promise,这样from()才能发出值,触发后续的操作符:

const axiosStub = sandbox.stub(axios, "post").resolves({});

或者用Promise.resolve也可以:

const axiosStub = sandbox.stub(axios, "post").returns(Promise.resolve({}));

2. 调整stub的设置时机

要在创建Epic的输入流和实际流之前设置stub,确保Epic执行时用的是stub后的axios:

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

  // 先设置stub
  const axiosStub = sandbox.stub(axios, "post").resolves({});

  // 再创建source和actual流
  const source = ts.cold(inputM, values); // 不需要ActionsObservable.from,ts.cold直接返回Observable
  const actual = updateRemoteFieldEpic(source);

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

3. 额外验证:确保requestData.customFields不为空

你的Epic里有个判断:if (requestData.customFields.length > 0)才会发起请求。测试里的fields是[{ value: "test string", id: 20 }],这个id不在requiredFieldIds(4、12)里,而且有value,所以会被保留,requestData.customFields长度是1,这个判断是通过的,没问题。

修改后的完整测试代码

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

      // 先设置axios stub,返回resolved的Promise
      const axiosStub = sandbox.stub(axios, "post").resolves({});

      // 使用ts.cold直接创建source Observable
      const source = ts.cold(inputM, values);
      const actual = updateRemoteFieldEpic(source);

      ts.expectObservable(actual).toBe(outputM, values);
      ts.flush();
      expect(axiosStub.called).toBe(true);
      // 还可以验证axios.post的参数是否正确
      expect(axiosStub.calledWith("/customfields/1", {
        id: 1,
        customFields: [{ value: "test string", id: 20 }]
      })).toBe(true);
    });
  });
});

补充说明

你提到“在map中返回action creator的Observable导致应用崩溃”,这是因为Redux期望action是普通对象,而不是Observable——所以你的Epic里用map(() => queueAlert(...))是正确的,因为queueAlert返回的是action对象,from(axios.post(...)).pipe(map(...))会发出这个action对象,而不是Observable。

内容的提问来源于stack exchange,提问作者vitjbr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:10:30