伪时间器通过替换真实时间依赖使定时器逻辑可预测、可快进,核心是接管 settimeout/setinterval 等 api;jest 用 jest.usefaketimers(),vitest 用 vi.usefaketimers(),mocha+sinon 用 sinon.usefaketimers() 获取 clock;需配合 runalltimers、advancetimersbytime 或 clock.tick 推进时间并触发回调;测试后须调用 clearalltimers 和 userealtimers(或 clock.uninstall)清理,避免污染;典型用法包括分步推进时间验证中间状态。

直接用伪时间器测试定时器逻辑,核心是替换真实时间依赖,让 setTimeout、setInterval 等行为可预测、可快进、不等待。
启用 fake timers 的基本方式
不同测试框架写法略有差异,但目标一致:接管全局定时器 API。
- Jest 中调用 jest.useFakeTimers(),它会拦截所有 setTimeout/setInterval 等调用,转为内部队列管理
- Vitest 中对应的是 vi.useFakeTimers(),语义和行为与 Jest 高度一致
- Mocha + Sinon 场景下,用 sinon.useFakeTimers() 获取 clock 实例,后续通过
clock.tick(ms)推进时间
推进时间的几种常用操作
光启用 fake timers 不够,必须显式触发定时器执行,才能验证回调是否如期运行。
- jest.runAllTimers():立即执行所有已注册但尚未触发的定时器(包括嵌套的 setTimeout)
- jest.advanceTimersByTime(ms):模拟经过指定毫秒,只触发该时间段内到期的定时器
- jest.runOnlyPendingTimers():只执行当前已到期、尚未执行的那一批(适合多层嵌套或需分步验证的场景)
- Sinon 的
clock.tick(ms)效果等同于advanceTimersByTime,更贴近“时钟走动”的直觉
测试后务必清理和还原
未清理的 fake timers 容易污染后续测试,尤其在 describe 块外全局启用时风险更高。
- 每个 test 结束前建议调用 jest.clearAllTimers(),清空待执行队列
- 整个 describe 或 afterEach 中调用 jest.useRealTimers(),确保下一组测试使用真实时间
- 若用 Sinon,记得
clock.uninstall()或在 afterEach 中 restore - 避免在 beforeAll 中启用 fake timers 而不在 afterAll 中还原,否则可能引发不可预知的超时或跳过
典型测试模式示例
以一个每秒调用一次 progress 回调、最后触发 done 的倒计时函数为例:
- 先 mock 回调函数:
const progress = jest.fn(); const done = jest.fn(); - 调用被测函数:
countdown(3, progress, done); - 推进 3000ms:
jest.advanceTimersByTime(3000); - 断言:
expect(progress).toHaveBeenCalledTimes(3); expect(done).toHaveBeenCalledTimes(1); - 若需验证中间状态(如第2秒时 progress 被调了几次),可在 1000ms 和 2000ms 分别 tick 并断言











