5月27日 19:52

Jest 怎么测试 setTimeout 和 setInterval?fake timers 怎么用?

Jest 用 jest.useFakeTimers()setTimeoutsetInterval 替换成模拟实现,然后通过 jest.runAllTimers()jest.advanceTimersByTime() 等方法手动推进时间,不用真等。

核心流程就三步:开启假定时器 → 写业务代码 → 手动推进时间并断言。

javascript
jest.useFakeTimers(); const callback = jest.fn(); setTimeout(callback, 1000); jest.advanceTimersByTime(1000); expect(callback).toHaveBeenCalledTimes(1);

runAllTimers 会一口气跑完所有待执行的定时器,包括嵌套的。如果你的代码里定时器会不断递归注册自己(比如轮询),用 runAllTimers 会死循环——这种情况用 runOnlyPendingTimers 只跑当前这轮。

advanceTimersByTime(ms) 更精确,只推进指定毫秒数,适合测"3 秒后应该执行了 3 次"这类场景:

javascript
const cb = jest.fn(); setInterval(cb, 1000); jest.advanceTimersByTime(3000); expect(cb).toHaveBeenCalledTimes(3);

每个测试用例结束记得恢复真实定时器:jest.useRealTimers(),不然会影响后续测试。推荐放 afterEach 里统一清理。

追问

useFakeTimers 和手动 mock setTimeout 有什么区别?

useFakeTimers 是 Jest 内置的,会替换全局的 setTimeout/setInterval/clearTimeout/clearInterval/setImmediate 等,提供 runAllTimersadvanceTimersByTime 等控制 API。手动 mock 只替换你 spyOn 的那一个函数,控制力更弱,需要自己模拟时间推进。

fake timers 和 Promise 混用时有什么坑?

这是最常见的坑:jest.useFakeTimers() 默认也会 fake 掉 process.nextTick 和微任务队列,导致 Promise.resolve().then(...) 里的回调不执行。Jest 27+ 可以用 jest.useFakeTimers({ doNotFake: ['nextTick'] }) 排除 nextTick,或者手动 await new Promise(process.nextTick) 让微任务跑完再推进时间。

jest.advanceTimersByTime 和 jest.runTimersToTime 有什么区别?

runTimersToTime 是旧 API(Jest 22 及之前),行为和 advanceTimersByTime 基本一致但语义模糊。Jest 23+ 推荐用 advanceTimersByTime,旧 API 仅为向后兼容保留。

实际项目里测定时器最容易犯什么错?

忘记在 beforeEach 里开启 fake timers,导致前一个测试的真实定时器泄漏到下一个测试;或者用 runAllTimers 跑有递归定时器的代码导致栈溢出。另一个常见问题是 afterEach 里只调了 useRealTimers 但没调 clearAllTimers,残留的定时器可能干扰后续用例。

标签:Jest