Jest测试含setInterval异步调用的TS代码:After输出异常及解决
Jest测试含异步操作的setInterval代码时日志顺序异常问题
场景与代码
我需要用Jest测试一段包含setInterval且内部存在异步操作的TypeScript代码,先写出了正常运行的代码:
export { } let startTime: number; let intervalTimer: NodeJS.Timer; async function f2(): Promise<void> { console.log(Date.now() - startTime); } async function f1(): Promise<void> { intervalTimer = setInterval(async () => { console.log("Before", Date.now() - startTime); await f2(); console.log("After", Date.now() - startTime); }, 200); } startTime = Date.now(); setTimeout(() => { clearInterval(intervalTimer); }, 900); f1();
这段代码运行后,控制台输出符合预期:
Before 201 202 After 202 Before 401 401 After 401 Before 602 602 After 602 Before 802 802 After 802
但编写Jest测试代码后,出现了异常:
let startTime: number; let intervalTimer: NodeJS.Timer; async function f2(): Promise<void> { console.log("f2", Date.now() - startTime); } async function f1(): Promise<void> { intervalTimer = setInterval(async () => { console.log("Before", Date.now() - startTime); await f2(); console.log("After", Date.now() - startTime); }, 200); } describe('Suite1', () => { beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); }); test('Case1', async () => { startTime = Date.now(); setTimeout(() => { clearInterval(intervalTimer); }, 900); await f1(); jest.advanceTimersByTime(900); }); });
控制台输出异常:所有Before和f2的日志依次输出后,所有After日志仅在最后集中输出,而非紧跟对应f2调用之后。
原因分析
Jest的jest.advanceTimersByTime()会一次性快进所有定时器,但异步函数的Promise回调会被推入微任务队列。当快进定时器时,所有setInterval的回调会被同步执行,执行到await f2()时,f2的同步日志会立即输出,但await后的console.log("After"...)会被放到微任务队列中。由于此时Jest还在同步处理所有定时器回调,微任务队列不会被清空,直到所有定时器回调执行完毕后,才会一次性处理所有微任务,导致所有After日志集中在最后输出。
解决方案
需要在每次定时器回调执行后,手动清空微任务队列,确保await后的代码能及时执行。可以通过在每次快进部分时间后,调用await Promise.resolve()来触发微任务队列的处理,或者分多次快进定时器并处理微任务。
修改后的测试代码如下:
describe('Suite1', () => { beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); }); test('Case1', async () => { startTime = Date.now(); setTimeout(() => { clearInterval(intervalTimer); }, 900); await f1(); // 分多次快进,每次快进后处理微任务 for (let i = 0; i < 4; i++) { jest.advanceTimersByTime(200); await Promise.resolve(); // 清空微任务队列,让After日志及时输出 } jest.advanceTimersByTime(100); // 快进剩余的100ms触发clearInterval }); });
这样修改后,每次setInterval回调执行到await f2()后,微任务会被及时处理,After日志就能紧跟对应f2的日志输出,和正常运行时的顺序一致。
内容的提问来源于stack exchange,提问作者carlo.milanesi
相关产品推荐
相关产品推荐

