如何对redux-saga中的delay函数进行单元测试?
首先,你遇到的测试失败问题,大概率是因为直接比较delay(1000)返回的对象引用或实例不相等——不管是Redux-Saga自带的delay(返回Effect对象)还是自定义的Promise版delay,每次调用都会生成新的对象,直接用toEqual断言自然会失败。下面分两种场景给出具体的解决方案:
场景1:使用Redux-Saga自带的delay(推荐)
Redux-Saga提供的delay是一个Effect创建器,返回的是特殊的Effect对象。测试这类Effect的最佳方式是使用官方的redux-saga/test-utils工具库,它提供了更简洁、可靠的测试API。
方法1:用expectSaga(最推荐,无需手动迭代Generator)
expectSaga可以直接测试Saga的完整流程,并且内置了断言delay的方法:
import { expectSaga } from 'redux-saga/test-utils'; import { delay } from 'redux-saga/effects'; import { incrementAsync } from './your-saga-file'; test('should trigger delay and increment', async () => { await expectSaga(incrementAsync) // 断言saga调用了1000ms的delay .toHaveCalledDelay(1000) // 运行saga并完成测试 .run(); });
方法2:手动迭代Generator(用Effect匹配器)
如果你习惯手动遍历Generator,可以用Redux-Saga提供的matchers来断言Effect的类型和参数,而不是直接比较对象:
import { isDelay } from 'redux-saga/test-utils'; import { delay } from 'redux-saga/effects'; import { incrementAsync } from './your-saga-file'; test('should incrementAsync', () => { const generator = incrementAsync(); // 断言第一个yield的是1000ms的delay Effect const delayEffect = generator.next().value; expect(isDelay(delayEffect, 1000)).toBe(true); // 继续后续的断言(比如触发increment action) // const incrementAction = generator.next().value; // expect(incrementAction).toEqual({ type: 'INCREMENT' }); });
场景2:自定义Promise版delay
如果你是自己实现的delay(比如返回Promise):
// 自定义delay const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
这种情况下,每次调用delay(1000)都会生成新的Promise实例,直接用toEqual肯定不相等。此时可以通过以下方式测试:
方法1:用expectSaga模拟delay
expectSaga允许你mock掉Promise版的delay,避免真实等待,同时断言它被调用:
import { expectSaga } from 'redux-saga/test-utils'; import { incrementAsync } from './your-saga-file'; import * as delayModule from './where-delay-is-defined'; // Mock自定义的delay函数 jest.spyOn(delayModule, 'delay').mockResolvedValue(); test('should call custom delay and increment', async () => { await expectSaga(incrementAsync) .run(); // 断言delay被调用了一次,参数是1000 expect(delayModule.delay).toHaveBeenCalledWith(1000); });
方法2:手动迭代Generator时断言Promise行为
如果你手动测试Generator,可以断言yield的值是Promise,并借助假计时器跳过真实等待:
import { incrementAsync } from './your-saga-file'; test('should incrementAsync', async () => { const generator = incrementAsync(); const yieldedPromise = generator.next().value; // 断言返回的是Promise expect(yieldedPromise).toBeInstanceOf(Promise); // 用假计时器验证Promise的延迟行为 jest.useFakeTimers(); yieldedPromise.then(() => { const incrementAction = generator.next().value; expect(incrementAction).toEqual({ type: 'INCREMENT' }); }); jest.advanceTimersByTime(1000); jest.useRealTimers(); });
总结
最省心的方式是使用Redux-Saga官方的expectSaga工具,它不仅能轻松断言delay的调用,还能模拟整个Saga流程,避免手动处理Generator的繁琐操作。如果是自定义的Promise版delay,记得用Jest的mock功能拦截并断言调用,同时借助假计时器跳过真实等待,提升测试速度。
内容的提问来源于stack exchange,提问作者Dimitri Kopriwa

