测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载本篇技术指南围绕 Sinon.JS fake timers 提供的clock.runMicrotasks()方法展开讲解它如何在不推进时钟的前提下同步执行process.nextTick、queueMicrotask与Promise回调等微任务并结合仓库内 run-microtasks.test.js 测试用例与 fake-timers.js 源码剖析其底层机制。读完本文你将掌握 fake timers 环境下微任务与宏任务timer的执行边界、runMicrotasks()与tick()/runAll()的配合方式以及在异步测试中避免回调悬挂的实战技巧。一、clock.runMicrotasks()是什么在 Sinon.JS 的 fake timers 体系中clock对象是对真实全局定时器setTimeout、setInterval、Date、Temporal等的同步替身用于在测试中精确控制时间。而clock.runMicrotasks()负责的是另一条独立的执行线——微任务队列microtask queue。根据 run-microtasks.md 的定义Runs all pending microtasks (e.g.process.nextTickorPromisecallbacks).即运行所有尚未执行的微任务典型代表是process.nextTick回调与Promise回调浏览器与 Node.js 中还包括queueMicrotask。这一调用是同步的调用结束后微任务队列被清空且不会推进时钟的时间。二、为什么需要手动刷新微任务JavaScript 事件循环将任务分为宏任务macrotask如setTimeout、setInterval、setImmediate与微任务microtask如Promise.then、queueMicrotask、Node.js 的process.nextTick。在真实环境中微任务由事件循环在每轮宏任务结束后自动清空但 fake timers 把定时器换成了同步实现时间由开发者手动拨动此时事件循环的自动微任务调度并不会同步发生。典型场景const clock sinon.useFakeTimers(); Promise.resolve().then(() { console.log(promise resolved); }); console.log(sync code); // 此时 Promise 回调尚未执行 —— 微任务不会自动刷新在 fake timers 控制下的同步测试里若业务代码依赖Promise或process.nextTick的延续逻辑就必须显式调用clock.runMicrotasks()来让这些回调落地否则会出现断言永远不满足的悬挂问题。三、基本行为与使用方式clock.runMicrotasks()由sinon.useFakeTimers()返回的clock对象提供使用方式极为简单import sinon from sinon; const clock sinon.useFakeTimers(); let called false; Promise.resolve().then(() { called true; }); // 此刻 called false clock.runMicrotasks(); // 此刻 called true微任务已被同步刷新 clock.restore();三个可验证的核心行为仓库中的测试文件 run-microtasks.test.js 用三个独立的 tap 测试精确锁定了该方法的行为边界刷新process.nextTick且不推进时间const clock sinon.useFakeTimers({ toFake: [nextTick] }); let called false; process.nextTick(() { called true; }); t.notOk(called, nextTick callback should not have run yet); clock.runMicrotasks(); t.ok(called, nextTick callback should have run); t.equal(clock.now, 0, clock time should not have advanced);注意最后一行断言clock.now仍为初始值0。这说明runMicrotasks()只清空微任务队列不拨动时钟与tick()的行为有本质区别。刷新queueMicrotask回调const clock sinon.useFakeTimers({ toFake: [queueMicrotask] }); let called false; queueMicrotask(() { called true; }); clock.runMicrotasks(); t.ok(called, queueMicrotask callback should have run);不会触发setTimeout这类宏任务const clock sinon.useFakeTimers(); let timerCalled false; setTimeout(() { timerCalled true; }, 0); clock.runMicrotasks(); t.notOk(timerCalled, setTimeout callback should not have fired);这是最重要的边界runMicrotasks()只处理微任务绝不触碰定时器。即便setTimeout(..., 0)已经到期排队也不会因为调用runMicrotasks()而执行。关于toFake配置的说明上述测试分别使用了{ toFake: [nextTick] }与{ toFake: [queueMicrotask] }。需要澄清这些配置影响的是哪些全局函数被替换成假实现而runMicrotasks()方法本身始终可用——它的职责是刷新当前 fake timers 上下文中的微任务队列与toFake的取值无直接依赖关系。依据 use-fake-timers.md 中的配置表默认情况下 fake timers 会替换除process.nextTick之外的所有可替换方法如需将nextTick也纳入替换范围可显式传入toFake: [setTimeout, clearTimeout, setInterval, clearInterval, setImmediate, clearImmediate, Date, nextTick, hrtime, performance]。四、与tick()/runAll()的定位差异理解runMicrotasks()最有效的方式是与 fake timers 家族的其他时间推进方法对照。下表汇总了仓库中相关文档的核心定位方法核心行为是否推进时钟是否执行定时器文档clock.runMicrotasks()刷新全部待处理微任务Promise / nextTick / queueMicrotask否否run-microtasks.mdclock.tick(time)将时钟向前推进time毫秒触发该时间范围内到期的定时器是是tick.mdclock.next()推进到下一个已调度定时器的时刻并触发它是是仅下一个next.mdclock.runAll()运行所有待处理定时器直到清空含执行期间新添加的是是run-all.mdclock.runToLast()运行到最后一个已调度定时器被触发是是run-to-last.md从源码结构看src/sinon/util/fake-timers.jsclock对象实际是sinonjs/fake-timers包install()的产物因此runMicrotasks()的实现来自上游 fake-timers 库Sinon 的useFakeTimers()只是负责组装配置并把它安装到全局对象上。异步变体与微任务的交互tick 系列还提供了异步变体tickAsync()、runAllAsync()、nextAsync()、runToLastAsync()。文档明确指出这些异步版本会中断事件循环让已调度的 Promise 回调在运行定时器之前执行ThetickAsync()will also break the event loop, allowing any scheduled promise callbacks to executebeforerunning the timers.也就是说当你使用异步变体推进时间时Promise 微任务会被自动放行而runMicrotasks()的价值恰恰在于同步场景——不需要async/await、不需要中断事件循环就能在当前调用栈内一次性清空微任务适合纯同步断言风格的测试。五、源码层面的佐证在 fake-timers.js 中可以看到 Sinon 如何把 fake timers 组装起来useFakeTimers(dateOrConfig)L40-L72支持三种调用形态无参数now: 0起步、传 epoch 数字或Date、传配置对象配置对象中的global字段会被拆出用于指定目标全局上下文如 JSDOM 的 window。createClock(config, globalCtx)L14-L22在给定全局上下文下调用FakeTimers.install(config)生成 clock并把restore别名到uninstall。文件末尾的timers对象L80-L97通过addIfDefined按环境能力收集可替换的全局函数其中就包括nextTick与queueMicrotask——这正是runMicrotasks()要刷新的两类微任务来源。由此可以确认Sinon 的 fake timers 本身就是sinonjs/fake-timers原 lolex的薄封装runMicrotasks()的行为由上游库保证Sinon 侧无需额外实现测试直接通过sinon.useFakeTimers()的返回值调用即可。六、实战场景一同步测试 Promise 链许多被测试函数会先执行同步逻辑再通过Promise.resolve().then()或 async 函数延续。在 fake timers 下同步断言常常会抢先于微任务执行import sinon from sinon; // 被测代码 function loadUser() { return Promise.resolve().then(() ({ id: 1, name: Ada })); } // 测试 const clock sinon.useFakeTimers(); let user null; loadUser().then((u) { user u; }); // 断言前先刷新微任务 clock.runMicrotasks(); if (user ! null) { console.log(user.name); // Ada } clock.restore();如果不调用runMicrotasks()user会一直是null测试就会产生误判。七、实战场景二Node.js 的process.nextTick与事件循环惯用法Node.js 生态中大量库依赖process.nextTick实现当前阶段末尾执行。结合 run-microtasks.test.js 第一个用例的写法可以用{ toFake: [nextTick] }精确替换该函数再用runMicrotasks()驱动const clock sinon.useFakeTimers({ toFake: [nextTick] }); const events []; process.nextTick(() events.push(nextTick)); clock.runMicrotasks(); console.log(events); // [nextTick] clock.restore();同理浏览器端或 Web Worker 环境中的queueMicrotask也可以用相同模式测试第二个用例即验证此场景。八、实战场景三在 Sandbox 中组合使用在真实测试套件中更推荐把 fake timers 挂到 sandbox 上借助 sandboxes/api/use-fake-timers.md 描述的sinon.createSandbox({ useFakeTimers: true })统一管理安装与恢复import sinon from sinon; const sandbox sinon.createSandbox({ useFakeTimers: true }); let resolved false; Promise.resolve().then(() { resolved true; }); sandbox.clock.runMicrotasks(); console.log(resolved); // true sandbox.restore(); // 统一恢复被替换的全局函数sandbox.clock与useFakeTimers()返回的 clock 行为一致runMicrotasks()均可直接调用。九、注意事项与最佳实践只刷微任务不碰定时器若测试同时存在setTimeout与 Promise 回调runMicrotasks()只处理后者定时器仍需tick()/runAll()驱动。不推进时间clock.now在调用前后保持不变因此它不会影响任何与绝对时间相关的断言。同步执行runMicrotasks()不是 async 方法调用返回时微任务队列已清空无需await。与 async 变体的取舍如果测试本身是 async 函数且需要先跑 Promise 回调再跑定时器的顺序可直接使用tickAsync()/runAllAsync()而纯同步测试优先选择runMicrotasks()代码更简洁、执行路径更可控。记得 restore无论通过clock.restore()还是sandbox.restore()都要在测试收尾时恢复被替换的全局函数避免污染其他用例参考 sandboxes/api/restore.md。十、相关 API 导航fake timers 安装与配置use-fake-timers.md时间推进tick.md、next.md、run-all.md、run-to-last.md时钟管理reset.md、restore.md、set-system-time.md概念总览fake-timers/index.md异步测试进阶fake-timers-async.md实现与测试fake-timers.js、run-microtasks.test.js小结clock.runMicrotasks()是 Sinon fake timers 中容易被忽略却非常关键的方法它把微任务刷新从事件循环的自动行为中解放出来让开发者在同步测试中精确控制Promise/process.nextTick/queueMicrotask回调的执行时机且不与时钟推进耦合。配合 run-microtasks.test.js 中锁定的三条行为边界刷新 nextTick、刷新 queueMicrotask、不触发 setTimeout你可以放心地在自己的测试代码中组合使用它与tick()/runAll()彻底掌控 fake timers 下的任务调度顺序。赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐策略综合Strategy Synthesis在 machine-learning-for-trading 中用统一流水线横评九大案例研究的信号、组合与成本存活策略综合Strategy Synthesis在 machine learning for trading 中用统一流水线横评九大案例研究的信号、组合与成本测试开发工具InfoSpider爬虫队列任务队列的实现与管理策略InfoSpider爬虫队列任务队列的实现与管理策略 引言爬虫任务管理的痛点与解决方案 在数据采集领域高效的任务调度机制是保障爬虫系统稳定性和性能的核心。网页爬虫数据分析数据可视化桌面应用brpc ExecutionQueue 深度解析wait-free 异步串行任务队列的原理与实战brpc ExecutionQueue 深度解析wait free 异步串行任务队列的原理与实战 brpcbetter RPC的 bthread 库提供了后端RPC框架通信网络上一篇NVIDIA-Nemotron-3-Nano-4B-GGUF性能测试在Jetson Thor上的惊人表现 下一篇AATree 深度解析iTerm2 中的 Arne Andersson 平衡二叉搜索树NSMutableDictionary 子类实现与应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
