使用modern模式useFakeTimers时异步测试超时问题求助
问题描述
此前了解过关于legacy计时器结合异步回调的相关讨论,但在React Native项目中使用Jest的modern计时器时遇到了超时问题。无论在beforeAll、beforeEach钩子还是异步测试用例内部启用jest.useFakeTimers("modern")(Jest 26版本)或jest.useFakeTimers()(Jest 29版本),只要在异步测试中启用fake timers,就会触发超时错误。
Jest配置
当前项目的Jest配置如下:
"jest": { "preset": "react-native", "modulePathIgnorePatterns": [ "<rootDir>/example/node_modules", "<rootDir>/lib/" ] }
错误信息
测试时抛出的超时错误:
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.
环境情况
- 初始使用Expo SDK 47对应的Jest 26.6.3
- 升级至
jest@29.3.1后问题仍未解决 - 已提交相关Bug报告
可行解决方法
1. 手动触发微任务队列
现代计时器模式下,Jest不会自动处理微任务(比如Promise回调),因此在推进计时器后需要手动触发微任务执行:
test('带计时器的异步测试', async () => { jest.useFakeTimers('modern'); // 执行包含定时器的异步函数 const asyncTask = yourAsyncFunctionWithTimer(); // 推进所有计时器 jest.runAllTimers(); // 触发微任务队列,处理Promise回调 await Promise.resolve(); // 等待异步任务完成并断言 const result = await asyncTask; expect(result).toBe(/* 预期结果 */); // 恢复真实计时器 jest.useRealTimers(); });
也可以使用jest.runAllTicks()替代await Promise.resolve(),效果一致。
2. 隔离单个测试的计时器状态
避免在全局钩子中启用fake timers,改为在每个测试用例内部单独启用和销毁,确保测试环境的独立性:
test('独立测试用例', async () => { jest.useFakeTimers('modern'); // 测试逻辑:触发包含定时器的操作 triggerTimerAction(); // 推进计时器并处理微任务 jest.runAllTimers(); await jest.runAllTicks(); // 执行断言 expect(someState).toBe(true); // 清理计时器 jest.useRealTimers(); });
3. 临时调整测试超时时间
如果上述方法无法立即生效,可以临时延长单个测试的超时时间,避免因微任务处理延迟导致的超时:
test('超时敏感的异步测试', async () => { // 测试逻辑 }, 10000); // 将超时时间设置为10秒
注意:这只是临时缓解方案,建议优先修复计时器和微任务的触发逻辑。
4. 检查残留定时器
确保在每个测试结束后清理所有fake timers,避免残留的定时器影响后续测试:
afterEach(() => { jest.useRealTimers(); jest.clearAllTimers(); });
内容的提问来源于stack exchange,提问作者Archimedes Trajano

