Marble测试中Action Creator未向流返回值问题求助
我仔细看了你的代码,问题主要出在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

