Jest中层叠定时器与异步函数测试:cb未触发如何修复?
Jest异步定时器测试问题
场景复现
第一段测试代码中,jest.runAllTimers()能模拟足够时间流逝,cb()可以正常触发:
test('test', async () => { jest.useFakeTimers(); const cb = jest.fn(); const func = jest.fn().mockResolvedValueOnce(1); setTimeout(async () => { setTimeout(() => { cb(); }, 1000); }, 1000); jest.runAllTimers(); });
但在下面的测试代码中,因为加入了await func()的异步逻辑,cb()从未被触发,测试无法通过:
test('test', async () => { jest.useFakeTimers(); const cb = jest.fn(); const func = jest.fn().mockResolvedValueOnce(1); setTimeout(async () => { await func(); setTimeout(() => { cb(); }, 1000); }, 1000); jest.runAllTimers(); expect(cb).toHaveBeenCalledTimes(1); });
问题原因
当setTimeout的回调是异步函数时,jest.runAllTimers()会同步执行这个回调,但遇到await时,函数会暂停并返回一个Promise。此时Jest的定时器模拟不会等待这个Promise完成,后续的内部setTimeout还没来得及被注册到定时器队列中,所以第一次runAllTimers()执行完后,没有待处理的定时器去触发cb。
解决方案
方法一:两次调用runAllTimers并等待微任务
test('test', async () => { jest.useFakeTimers(); const cb = jest.fn(); const func = jest.fn().mockResolvedValueOnce(1); setTimeout(async () => { await func(); setTimeout(() => { cb(); }, 1000); }, 1000); // 第一次执行外层setTimeout的回调 jest.runAllTimers(); // 等待微任务队列执行完毕,确保await func()完成,内部setTimeout被注册 await Promise.resolve(); // 第二次执行所有已注册的定时器,触发cb jest.runAllTimers(); expect(cb).toHaveBeenCalledTimes(1); });
方法二:分步推进时间并等待微任务
这种方式更精准地控制时间推进的节点:
test('test', async () => { jest.useFakeTimers(); const cb = jest.fn(); const func = jest.fn().mockResolvedValueOnce(1); setTimeout(async () => { await func(); setTimeout(() => { cb(); }, 1000); }, 1000); // 推进1000ms,触发外层setTimeout的回调 jest.advanceTimersByTime(1000); // 等待await的Promise完成,让内部setTimeout被注册 await Promise.resolve(); // 再推进1000ms,触发内部setTimeout的回调,执行cb jest.advanceTimersByTime(1000); expect(cb).toHaveBeenCalledTimes(1); });
两种方法的核心都是:等待异步回调中的微任务完成,确保内部的定时器被注册后,再执行定时器模拟逻辑。
内容的提问来源于stack exchange,提问作者Emre Tapcı
相关产品推荐
相关产品推荐

