Jest与redux-saga-test-plan中如何mock saga生成器函数
问题根因
你写的jest.spyOn不生效的核心原因:
chooseCorrectGenerator内部直接调用了同模块的behaviour1,引用的是模块内的原始函数,不是模块导出对象上的属性redux-saga-test-plan运行saga时会直接迭代生成器执行,不会走模块导出的引用路径,所以你挂载在导出对象上的spy完全不会被触发
方案1:正确mock子saga
如果确实需要mockbehaviour1的实现,不要用jest.spyOn,直接用redux-saga-test-plan自带的provide拦截器,它会在saga运行时拦截所有saga调用、副作用触发,不需要修改模块引用:
import * as sagaFunctions from './saga'; import { expectSaga } from 'redux-saga-test-plan'; test('flag为true时调用behaviour1', async () => { // 定义mock实现,不需要执行原saga逻辑 const mockBehaviour1 = jest.fn(function* () {}); await expectSaga(sagaFunctions.chooseCorrectGenerator, { type: "TEST_ACTION", payload: { flag: true } }) .provide({ // 拦截所有saga委托/调用 saga(effect, next) { if (effect.fn === sagaFunctions.behaviour1) { mockBehaviour1(effect.args); return; } return next(); }, // 如果业务代码里有select、put、call等副作用,也可以在这里统一mock返回值 select() { return {}; // 按业务需要返回mock的store数据 } }) .run(); expect(mockBehaviour1).toBeCalledTimes(1); });
方案2:更推荐的无mock测试方式
你要测试的核心是chooseCorrectGenerator的分支路由逻辑,完全不需要mockbehaviour1,两种写法更简单健壮:
写法1:静态步进测试(优先选)
用testSaga做静态单元测试,只会逐行校验当前saga产出的effect,不会真的执行子saga逻辑,运行速度极快,也不需要任何mock:
import { testSaga } from 'redux-saga-test-plan'; import * as sagaFunctions from './saga'; test('flag为true时委托behaviour1', () => { const testAction = { type: "TEST_ACTION", payload: { flag: true } }; testSaga(sagaFunctions.chooseCorrectGenerator, testAction) .next() // 断言当前saga产出了调用behaviour1的effect,参数正确 .call(sagaFunctions.behaviour1, testAction) .next() .isDone(); }); test('flag为false时委托behaviour2', () => { const testAction = { type: "TEST_ACTION", payload: { flag: false } }; testSaga(sagaFunctions.chooseCorrectGenerator, testAction) .next() .call(sagaFunctions.behaviour2, testAction) .next() .isDone(); });
这种写法完全符合你的场景:你已经给behaviour1/behaviour2写了独立测试,当前测试只需要验证路由逻辑正确即可,不需要关心子saga内部实现。
写法2:集成式断言
如果需要做更贴近真实运行的集成测试,不需要mock子saga,只需要给子saga用到的select等副作用提供mock数据,断言最终触发的put等结果即可:
test('flag为true时走behaviour1逻辑流', async () => { await expectSaga(sagaFunctions.chooseCorrectGenerator, { type: "TEST_ACTION", payload: { flag: true } }) .provide({ // 只mock全局副作用,不mock子saga select() { return { /* mock业务需要的store状态 */ }; } }) // 断言behaviour1逻辑最终会触发的action // .put({ type: 'BEHAVIOUR1_SUCCESS' }) // 反向断言不会触发behaviour2的结果 .not.put({ type: 'BEHAVIOUR2_SUCCESS' }) .run(); });
注意事项
- 不要用
jest.spyOn、jest.mock去mock同模块内的saga函数,这类mock无法拦截模块内部的函数调用,只会导致测试逻辑脆弱 - 路由类saga优先用静态步进测试,逻辑清晰、运行快、不会被子saga的内部变动影响
- 只有当需要验证跨saga的完整逻辑流时,才用集成式的
expectSaga写法
内容的提问来源于stack exchange,提问作者bogdan damaschin
相关产品推荐
相关产品推荐

