Redux Saga生成器函数Jest测试:重复yield fetch问题排查
Redux Saga生成器函数测试问题解答
问题场景
目标Saga代码
export function* mainFunction({ payload: { authorization } }) { try { const response = yield call( fetch, `${url}/api`, { headers: { Authorization: authorization , }, } ); if (response.status !== 200) { throw new Error("Failed to load"); } const jsonResponse = yield response.json(); const data = yield call( helperFunction1, Array.from(jsonResponse) ); const updatedData = yield call(helperFunction2, data); yield put(setData({ finalData: updatedData })); yield put(helperFunction3({ authorization })); } catch ({ message }) { yield showError(message); yield put(setError(message)); } }
对应Jest测试代码
const payload = { authorization: "authorization", }; const apiCall = call( fetch, `${url}/api`, { headers: { Authorization: authorization, }, } ); describe("success", () => { const saga = mainFunction({ payload }); const data = "data"; const response = { status: 200, json: () => data, }; const jsonResponse = response.json(); it("should call API", () => { expect(saga.next().value).toEqual(apiCall); }); it("should get response with data", () => { expect(saga.next(response).value).toEqual(data); }); it("should call helperFunction1", () => { expect(saga.next(jsonResponse).value).toEqual( call(helperFunction1, Array.from(jsonResponse)) ); }); it("should call helperFunction2", () => { expect(saga.next(data).value).toEqual(call(helperFunction2, data)); }); it("should dispatch success action with data", () => { expect(saga.next(data).value).toEqual( put(setData({ finalData: data })) ); }); it("should dispatch status check", () => { expect(saga.next(data).value).toEqual( put(helperFunction3({ accessToken: "token" })) ); }); it("should be done", () => { expect(saga.next().done).toBe(true); }); });
问题现象
前两个测试用例运行正常,但第三个验证helperFunction1调用的用例中,实际yield的是fetch调用(第一个测试的预期结果),而非预期的helperFunction1调用;后续测试却能正常通过。所有辅助函数均非生成器函数。
问题原因
核心问题是生成器实例的状态在测试用例间没有隔离:
- 你在
describe块顶部只初始化了一次saga生成器实例,但Jest的每个it测试用例是独立执行的,用例间不会保留生成器的推进状态。 - 第一个
it调用saga.next(),消耗了生成器的第一个yield(fetch调用);但第二个it执行时,拿到的是初始状态的生成器,直接调用saga.next(response)相当于跳过了fetch的yield步骤,导致生成器内部逻辑混乱。 - 到第三个
it时,生成器又回到初始状态,调用saga.next(jsonResponse)自然会得到第一个yield的fetch调用,这就是你看到重复yield fetch的原因。 - 后续测试看似正常,只是因为每次操作的都是新的初始生成器,碰巧参数和步骤对上了,但整个测试逻辑完全错误。
解决方法
在每个测试用例内部或beforeEach钩子中重新初始化生成器,确保每个测试都从生成器的初始状态开始,按正确顺序推进:
describe("success", () => { let saga; const payload = { authorization: "authorization" }; const data = "data"; const response = { status: 200, json: () => data }; const jsonResponse = response.json(); const apiCall = call(fetch, `${url}/api`, { headers: { Authorization: payload.authorization }, }); // 每个测试前重新创建生成器实例 beforeEach(() => { saga = mainFunction({ payload }); }); it("should call API", () => { expect(saga.next().value).toEqual(apiCall); }); it("should get response with data", () => { saga.next(); // 先推进到fetch的yield expect(saga.next(response).value).toEqual(jsonResponse); }); it("should call helperFunction1", () => { saga.next(); // 推进fetch步骤 saga.next(response); // 推进response.json()步骤 expect(saga.next(jsonResponse).value).toEqual( call(helperFunction1, Array.from(jsonResponse)) ); }); it("should call helperFunction2", () => { saga.next(); saga.next(response); saga.next(jsonResponse); expect(saga.next(data).value).toEqual(call(helperFunction2, data)); }); it("should dispatch success action with data", () => { saga.next(); saga.next(response); saga.next(jsonResponse); saga.next(data); expect(saga.next(data).value).toEqual( put(setData({ finalData: data })) ); }); it("should dispatch status check", () => { saga.next(); saga.next(response); saga.next(jsonResponse); saga.next(data); saga.next(data); expect(saga.next().value).toEqual( put(helperFunction3({ authorization: payload.authorization })) ); }); it("should be done", () => { saga.next(); saga.next(response); saga.next(jsonResponse); saga.next(data); saga.next(data); saga.next(); expect(saga.next().done).toBe(true); }); });
另外注意修正原测试中的一个小错误:最后一个put(helperFunction3)的参数应该是{ authorization }而非{ accessToken: "token" },和目标Saga代码保持一致。
内容的提问来源于stack exchange,提问作者AppSupportGuru1995
相关产品推荐
相关产品推荐

