ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Sinon fake timers 时间推进指南:深入解析 clock.tick() 与 clock.tickAsync()

Sinon fake timers 时间推进指南:深入解析 clock.tick() 与 clock.tickAsync() 测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载clock.tick(time)是 Sinon.JS fake timers 中最重要的时间控制手段之一它能把虚拟时钟向前拨动指定毫秒数并同步触发落在该时间范围内的全部定时器回调。本文以 docs/concepts/fake-timers/tick.md 为主线结合 src/sinon/util/fake-timers.js 的实现与 tick 测试用例完整讲解 tick 的三种参数形式数字、人类可读字符串、Temporal.Duration、tickAsync的事件循环语义、与其他时钟方法runAll、runToLast、next的取舍以及实战中的注意事项帮助你写出确定性强、不依赖真实等待的 JavaScript 时间相关测试。一、tick 是什么在虚拟时钟上快进时间fake timers 是setTimeout、setInterval、Date等全局时间 API 的同步替换实现由 Sinon 在安装时替换到全局对象上详见 docs/concepts/fake-timers/index.md。它返回一个clock对象而clock.tick(time)就是驱动这个虚拟时钟前进的核心方法将虚拟时钟向前推进time毫秒同步触发所有被调度在受影响时间范围内的定时器回调调用完成后时钟的当前时间等于原时间加上time。与真实测试中等待真实时间流逝不同tick 是同步的、确定的——无论定时器延时是 5 毫秒还是 5 小时都只需一次方法调用即可跳过这段等待。这正是 docs/concepts/fake-timers/run-all.md 所强调的fake timers 让异步测试可以运行到完成而不必担心定时器的数量或延时大小。最小可运行示例import sinon from sinon; const clock sinon.useFakeTimers(); // 时钟从 UNIX epoch0开始 let callCount 0; setTimeout(() callCount, 100); setTimeout(() callCount, 200); console.log(callCount); // 0 —— 尚未 tick回调不会触发 clock.tick(100); console.log(callCount); // 1 —— 第一个100ms定时器触发 clock.tick(100); console.log(callCount); // 2 —— 第二个200ms定时器触发 clock.restore(); // 恢复真实定时器上面这段逻辑与 docs/tests/docs/fake-timers/api/tick.test.js 中的第一个测试完全对应先断言 tick 前回调未触发再分别 tick 100ms 验证第一个回调、再 tick 100ms 验证第二个回调最后clock.restore()还原全局定时器。二、tick 的三种参数形式time可以是以下三种类型之一这也是 tick.md 明确列出的能力1. 毫秒数字最常用clock.tick(100); // 前进 100ms clock.tick(60_000); // 前进 60 秒数字即前进的毫秒数。测试中常配合setTimeout/setInterval的毫秒延时直接使用。2. 人类可读字符串tick 支持形如HH:MM:SS的时间字符串可直观表达较长的时间跨度无需手动换算毫秒。合法格式包括字符串含义088 秒01:001 分钟02:34:102 小时 34 分 10 秒let called false; setTimeout(() (called true), 5000); clock.tick(00:00:05); // 等于 clock.tick(5000) console.log(called); // true该用法在 tick 测试中有完整验证00:00:05表示 5 秒tick 之后回调应已触发。对于需要表达几分钟后过期几小时后重试这类业务语义的测试字符串形式比手写毫秒数字可读性更好。3. Temporal.Duration 对象当代码使用 Temporal API 表达时长时tick 可直接接受Temporal.Duration实例而无需手工换算clock.tick(Temporal.Duration.from({ hours: 1, minutes: 30 })); // 等价于 clock.tick(5_400_000)注意Temporal.Duration只有在运行环境支持 Temporal或已安装 polyfill时才可用。这也是 fake-timers/index.md 中提到的——fake timers 不仅能控制Date还能控制Temporal系列 API如Temporal.Now.instant()。三、tickAsync打破事件循环让 Promise 先执行clock.tick()是完全同步的它在单次调用栈内推进时钟并触发定时器。这带来一个经典问题——如果定时器回调内部依赖 Promise 的微任务microtask回调同步 tick 时这些 Promise 回调可能还来不及执行。await clock.tickAsync(time)正是为这一场景设计的与tick一样推进时钟并触发时间范围内的定时器额外打破事件循环break the event loop让已调度的 Promise 回调有机会在运行定时器之前执行。const clock sinon.useFakeTimers(); let asyncCalled false; setTimeout(() (asyncCalled true), 100); await clock.tickAsync(100); // 打破事件循环Promise 回调先执行 console.log(asyncCalled); // true这段代码与 tick 测试中的异步用例一致。需要说明的是tickAsync是异步方法调用处必须使用await或.then因此只能用在异步测试函数中。同步与异步的选型建议回调是纯同步逻辑、不依赖微任务时优先用同步的clock.tick()断言直接、无竞态回调会await某个 Promise、或依赖Promise.resolve().then(...)链时使用await clock.tickAsync()同一文档体系中runAllAsync、runToLastAsync也遵循同样的打破事件循环语义可参阅 run-all.md 与 run-to-last.md。四、源码视角fake timers 从哪来clock.tick本身由 Sinon 底层的sinonjs/fake-timers包实现当前仓库依赖版本为^15.4.0见 package.jsonSinon 的职责是负责安装与集成。src/sinon/util/fake-timers.js 中的createClock展示了安装过程的核心function createClock(config, globalCtx) { let FakeTimersCtx FakeTimers; if (globalCtx ! null typeof globalCtx object) { FakeTimersCtx FakeTimers.withGlobal(globalCtx); } const clock FakeTimersCtx.install(config); clock.restore clock.uninstall; return clock; }关键点默认在全局对象上安装 fake timers若传入config.global如 JSDOM 的window则通过FakeTimers.withGlobal(globalCtx)在指定对象上安装安装后返回的clock被绑定restore clock.uninstall因此测试收尾统一调用clock.restore()即可恢复所有被替换的全局时间 API默认安装时useFakeTimers()不传参数会以now: 0UNIX epoch启动时钟见 fake-timers.js。sinonjs/fake-timers项目本身由 Sinon.JS 早期版本拆分而来Sinon 在底层复用它因此clock.tick/tickAsync的行为与独立使用 fake-timers 包时完全一致。五、安装与配置useFakeTimers 入门tick 之前必须先安装 fake timers。常用形式如下详见 docs/concepts/fake-timers/use-fake-timers.md// 1. 不带参数时钟从 UNIX epoch0开始 const clock sinon.useFakeTimers(); // 2. 指定起始时间数字或 Date const clock2 sinon.useFakeTimers(1483228800000); // 2017-01-01 // 3. 配置对象起始时间 loopLimit const clock3 sinon.useFakeTimers({ now: 1483228800000, loopLimit: 10, }); // 4. 只替换部分 API如连 nextTick 一起替换 const clock4 sinon.useFakeTimers({ toFake: [setTimeout, clearTimeout, setInterval, clearInterval, Date, nextTick], });常用配置项速查完整表格见 use-fake-timers.md配置项类型默认值说明config.nowNumber/Date0虚拟时钟的起始 UNIX 时间戳config.toFakeString[]除nextTick外全部显式指定要替换的函数名不能与toNotFake同用config.toNotFakeString[][]显式指定保持原生的函数名config.loopLimitNumber1000runAll()最多运行定时器的数量上限config.shouldAdvanceTimeBooleanfalse根据真实系统时间自动推进虚拟时间通过约 20ms 的setInterval检查实现非实时推进config.targetObjectglobal指定安装目标对象JSDOM 等沙箱环境很有用其中toFake直接影响 tick 的行为范围只有被替换fake的 API 才会受clock.tick控制。例如{ toFake: [setTimeout, nextTick] }时process.nextTick的回调不会自动执行需要通过clock.runAll()手动冲刷见 docs/tests/docs/fake-timers/api-1.test.js。六、tick 与其他时钟方法的分工fake timers 的 clock 提供了多种推进时间的方式tick 是其中最精确的一种精确到毫秒。完整方法族参见 docs/concepts/fake-timers/index.md方法行为适用场景clock.tick(ms)精确推进指定毫秒数触发区间内定时器需要精确控制时间推进量的单元测试clock.next()推进到下一个最近定时器的触发时刻只关心下一个事件的测试clock.jump(ms)直接跳转时间但不触发中间定时器需要快速跨越时间而不关心中间回调时clock.runAll()运行所有待执行定时器直到队列清空不关心具体延时只想跑完所有定时器clock.runToLast()运行直到最后一个定时器触发关心最终状态、不关心中间步骤时clock.setSystemTime()修改系统时间但不触发定时器仅需调整Date/Temporal取值时选型核心逻辑需要验证某段时间之后发生什么如1 分钟后过期动画 500ms 后完成→ 用tick需要把所有定时器跑完、不关心具体延时 → 用runAll涉及 Promise 微任务时记得选择对应的 Async 版本tickAsync/runAllAsync/runToLastAsync。七、实战场景与注意事项场景一验证延时动画/节流逻辑以 docs/tests/docs/fake-timers/_index.test.js 中的动画为例const clock sinon.useFakeTimers(); function animate(element) { setTimeout(() { element.style.height 200px; element.style.width 200px; }, 500); } animate(el); console.log(el.style.height); // —— 尚未完成 clock.tick(510); // 前进 510ms覆盖 500ms 的动画延时 console.log(el.style.height); // 200px —— 动画完成这个测试的妙处在于整个过程是同步、瞬时完成的但被测代码看到的却是真实的500ms 流逝。场景二定时轮询 / 重试逻辑let attempts 0; const retry () setTimeout(() attempts, 1000); retry(); clock.tick(1000); // 第一次重试 retry(); clock.tick(1000); // 第二次重试 console.log(attempts); // 2注意事项务必 restore每个测试结束后调用clock.restore()恢复全局定时器避免 fake 泄漏到其他用例测试框架的afterEach是常见放置点。tick 后断言同步结果使用同步tick时回调触发是同步的断言可以紧跟在tick之后若回调内部依赖微任务请改用await tickAsync。tick 与Date联动fake timers 同时替换Date因此clock.tick(60000)之后new Date().getTime()也会前进 60 秒——这一点常用于测试基于当前时间的过期判断。字符串与 Temporal 的可用性字符串格式08/01:00/02:34:10只表示 时:分:秒不能直接写500表示 500 秒Temporal.Duration参数则要求环境支持 Temporal。shouldAdvanceTime的语义该配置通过内部setInterval默认 20ms 间隔轮询真实时间变化来推进虚拟时间并非让tick失效——需要确定性推进时仍应显式调用tick/tickAsync。八、总结clock.tick(time)与await clock.tickAsync(time)是 Sinon fake timers 中最精确、最常用的一对时间推进 API前者同步快进并触发区间内定时器后者额外打破事件循环以配合 Promise 微任务。三种参数形式毫秒数字、HH:MM:SS字符串、Temporal.Duration覆盖了从单元测试到现代 Temporal API 的各类需求。理解 tick 与runAll/runToLast/next的差异并结合toFake、loopLimit等配置use-fake-timers.md即可写出不依赖真实等待、运行毫秒级完成且完全确定的时间相关测试。赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐Sinon fake timers 进阶clock.runToLast() / runToLastAsync() 完整实战指南Sinon fake timers 进阶 clock.runToLast / runToLastAsync 完整实战指南 本篇技术指南聚焦 Sinon.JS测试开发工具Sinon fake timers 的 clock.now 属性读取当前假时间戳的完整指南Sinon fake timers 的 clock.now 属性读取当前假时间戳的完整指南 clock.now 是 Sinon fake timers 返回的测试开发工具Sinon Fake Timers 完全指南用可控时钟同步测试 setTimeout、Date 与 TemporalSinon Fake Timers 完全指南用可控时钟同步测试 setTimeout、Date 与 Temporal Sinon.JS 的 Fake Time测试开发工具上一篇grunt-bump安全发布指南用dry-run试运行模式3步避免版本号发布事故下一篇gh_mirrors/sh1/sh的插件权限控制细粒度的功能访问控制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表