- 测试
- 开发工具
【免费下载链接】sinon
Test spies, stubs and mocks for JavaScript.
sandbox.verify()是 sinon 沙箱(sandbox)体系中用于一次性校验所有经由该沙箱创建的 Mock 期望是否被满足的入口方法。在默认沙箱模式下,它直接以sinon.verify()的形式调用,是编写基于 Mock 的单元测试时收尾断言的关键一步。读完本文,你将掌握verify的调用方式、失败时的异常行为与错误信息格式、它与其他沙箱方法(restore、verifyAndRestore)的分工,以及其底层实现原理(对应源码 src/sinon/sandbox.js 与 src/sinon/mock.js)。
沙箱与sandbox.verify的定位
在深入verify之前,需要先明确它所属的沙箱模型。自sinon@5.0.0起,sinon根对象本身就是一个默认沙箱(default sandbox),除非你有非常特殊的配置需求,否则通常只需要直接使用它,而不必调用sinon.createSandbox()创建新沙箱(参见 Sandbox API 总览 与 createSandbox 说明)。
沙箱的核心价值在于分组管理:通过同一个沙箱创建的所有 fakes(fake / spy / stub)与 mocks,可以统一执行restore、reset、verify等批量操作,避免逐一清理的繁琐与遗漏。
sandbox.verify负责的正是其中"校验"这一环,其官方语义是:
Verifies all mocks created through the sandbox. (校验所有经由该沙箱创建的 Mock。)
需要特别强调的是:sandbox.verify()只校验沙箱内创建的 Mock(及其期望 expectation),它不会去校验 spy/stub 的调用断言——那些断言通常通过sinon.assert或expect库完成。这一点从命名与实现上都与mock.verify(校验单个 Mock 的全部期望)形成对应关系。
基本用法:在默认沙箱上调用sinon.verify()
由于sinon根对象就是默认沙箱,sandbox.verify()在日常代码中通常写作sinon.verify()。下面的完整示例取自仓库中的对应测试 docs/tests/docs/sandboxes/api/verify.test.js,它演示了"创建 Mock → 设置期望 → 校验失败抛出异常"的完整链路:
import tap from "tap"; import * as sinon from "sinon"; tap.test("sandbox.verify - verifies all mocks", (t) => { // The sinon root object is a default sandbox const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); const expectation = mock.expects("greet"); // verify will throw because of the unmet expectation t.throws( () => sinon.verify(), /Expected greet\('\[...\]'\) once \(never called\)/, "throws when expectation not met" ); sinon.restore(); t.end(); });逐段拆解这个流程:
- 创建 Mock:
sinon.mock(obj)基于普通对象obj生成一个 Mock,并挂到默认沙箱的收集列表中; - 设置期望:
mock.expects("greet")声明对greet方法的一次调用期望(默认once,即期望恰好被调用一次); - 触发校验:由于示例中从未调用
obj.greet(...),期望未被满足,sinon.verify()会抛出异常; - 验证异常:
t.throws(...)断言抛出的异常信息匹配/Expected greet\('\[...\]'\) once \(never called\)/; - 清理收尾:
sinon.restore()恢复所有被替换/包装的方法,避免污染其他测试。
若期望被满足(例如在调用verify()之前执行了obj.greet("World")),则sinon.verify()静默返回、不抛异常,测试正常通过。
失败行为:异常抛出与错误信息格式
verify与普通断言的关键差异在于失败时的行为方式:当沙箱内任一 Mock 的任一期望未被满足时,sandbox.verify()会抛出异常(throw),而不是返回布尔值或仅打印警告。
异常消息会明确指出哪个方法、以何种参数、期望调用几次、实际调用情况。以上述示例为例,错误信息形如:
Expected greet('[...]') once (never called)这条信息的结构对应 sinon Mock 期望(expectation)的字符串化输出:
greet:被期望的方法名;'[...]':期望接收的参数模式([...]表示参数占位/匹配规则);once:期望的调用次数(默认是恰好一次);never called:实际调用情况(从未被调用)。
如果存在多个未满足的期望,错误信息会将它们逐条列出并聚合。从 src/sinon/mock.js 第 94–114 行的mock.verify实现可以看到:它对每个代理方法(proxy)逐一检查其全部期望,未满足的期望字符串被压入messages,已满足的进入met,随后:
if (messages.length > 0) { mockExpectation.fail(join(concat(messages, met), "\n")); } else if (met.length > 0) { mockExpectation.pass(join(concat(messages, met), "\n")); }即只要有任一期望未满足,就通过mockExpectation.fail以换行分隔的聚合消息抛出失败;全部满足则走pass分支正常返回。
与mock.verify的层级关系
理解sandbox.verify的最佳方式,是与单点方法mock.verify对照:
| 方法 | 作用范围 | 失败行为 | 附带效果 |
|---|---|---|---|
mock.verify() | 单个 Mock 对象上的全部期望 | 抛出异常 | 会恢复该 Mock 包装的所有方法(见 mock.verify 文档) |
sandbox.verify() | 沙箱内收集的所有Mock | 抛出异常(首个/聚合失败) | 本身不执行恢复,需另行调用restore |
mock.verify在 src/sinon/mock.js 中还有一个值得注意的细节:校验完成时会调用this.restore()恢复被 Mock 替换的方法。而sandbox.verify在 src/sinon/sandbox.js 第 289–291 行的实现则非常简洁:
sandbox.verify = function verify() { applyOnEach(collection, "verify"); };它通过applyOnEach对沙箱收集列表collection中的每个条目调用其verify方法——所以沙箱级校验本质上就是"把mock.verify批量应用一遍"。沙箱自身不附带恢复逻辑,因此测试中通常要在verify之后(或通过verifyAndRestore)调用restore。
verifyAndRestore:校验与恢复一步到位
针对"校验失败也要保证清理"的常见场景,沙箱提供了组合方法sandbox.verifyAndRestore()(详见 verify-and-restore 文档),其语义为:
Verifies all mocks and restores all fakes created through the sandbox. (校验所有 Mock,并恢复所有经沙箱创建的 fakes。)
它的关键特性是:即使校验失败抛出了异常,恢复动作依然会执行。仓库测试 docs/tests/docs/sandboxes/api/verify-and-restore.test.js 完整验证了这一行为:
const obj = { greet: function (name) { return `Hello ${name}`; } }; const mock = sinon.mock(obj); const expectation = mock.expects("greet"); // mocked methods have a restore method on them t.equal(typeof obj.greet.restore, "function", "mocked method has restore function"); // verify will throw because of the unmet expectation t.throws( () => sinon.verifyAndRestore(), /Expected greet\('\[...\]'\) once \(never called\)/, "throws when expectation not met" ); // but the restore part will still be performed t.equal(typeof obj.greet.restore, "undefined", "method restored even though verify failed");注意测试中的两个断言点:
- Mock 包装后的方法带上了
restore函数(typeof obj.greet.restore === "function"); - 即使
verifyAndRestore()因期望未满足而抛错,原方法也已被恢复(typeof obj.greet.restore === "undefined")。
其实现原理在 src/sinon/sandbox.js 第 293–307 行,通过try/catch捕获verify抛出的异常,无论成败都执行sandbox.restore(),最后再把异常重新抛出:
sandbox.verifyAndRestore = function verifyAndRestore() { let exception; try { sandbox.verify(); } catch (e) { exception = e; } sandbox.restore(); if (exception) { throw exception; } };这样的设计保证了:单个用例即使 Mock 校验失败,也不会把被替换的方法残留到后续用例中,从而避免"失败级联污染"。
源码级原理:applyOnEach与 Mock 期望校验流程
如果把sandbox.verify的调用链展开,可以得到清晰的执行脉络:
sinon.verify()→sandbox.verify()(src/sinon/sandbox.js);applyOnEach(collection, "verify"):遍历沙箱收集列表,对每个条目调用其verify方法;- 对 Mock 条目,进入
mock.verify(src/sinon/mock.js):- 遍历该 Mock 的每个代理方法(
this.proxies); - 遍历每个方法的全部期望(
expectations[proxy]),调用expectation.met()判断是否满足; - 未满足的期望字符串进入
messages,满足的进入met; - 调用
this.restore()恢复被替换的方法; - 若存在未满足期望,聚合所有消息后
mockExpectation.fail(...)抛出异常;
- 遍历该 Mock 的每个代理方法(
- 异常向上传播,
sandbox.verify()调用点直接收到抛出结果。
值得注意的边界行为:
verify只作用于沙箱内创建的 Mock。沙箱同样收集 spy/stub(它们也出现在collection中),但 spy/stub 并不具备verify语义,因此批量校验对它们无实际效果——这正是文档明确定义"校验所有 Mock"的原因;sandbox.restore()不接受任何参数,如果误传参数会抛出"sandbox.restore() does not take any parameters. Perhaps you meant stub.restore()"的提示错误(见 src/sinon/sandbox.js),这也能帮助新手区分"沙箱级恢复"与"单个 stub 恢复"。
最佳实践与注意事项
结合文档与源码实现,使用sandbox.verify()时建议遵循以下实践:
- 默认优先用根沙箱:多数测试直接
sinon.mock()+sinon.verify()即可,无需createSandbox()(create-sandbox.md 明确警告:除非有非常高级的场景,否则应使用sinon对象上的默认沙箱); - 善用
verifyAndRestore做收尾:在afterEach/t.teardown等清理钩子中调用sinon.verifyAndRestore(),可以同时完成"断言全部期望 + 恢复全部替换",且失败时也不遗漏恢复; - 分离校验与恢复的关注点:如果希望先看到所有失败信息再做清理,或需要自定义恢复顺序,可分开调用
sinon.verify()(置于断言区)与sinon.restore()(置于清理区); - 理解错误信息的可读性:聚合的失败消息按方法逐个列出期望与调用情况,排错时优先看
never called、called with wrong arguments等关键词定位未满足的期望; - 不要指望
verify校验 spy/stub 断言:spy/stub 的调用次数、参数等断言请使用sinon.assert或mock的期望机制,sandbox.verify只覆盖 Mock 期望。
通过默认沙箱 +sinon.verify()(或sinon.verifyAndRestore())的组合,你可以用最少的样板代码实现对 Mock 期望的集中校验与资源的可靠清理,这正是 sinon 沙箱设计中最实用的能力之一。
- 测试
- 开发工具
【免费下载链接】sinon
Test spies, stubs and mocks for JavaScript.
相关推荐
如何高效配置Daytona沙箱期望状态:目标设定与特性配置完全指南
如何高效配置Daytona沙箱期望状态:目标设定与特性配置完全指南 Daytona作为开源开发环境管理器,其沙箱功能允许开发者创建隔离、可定制的开发环境。沙箱期
miniblink49 仓库中的 Google Mock(gmock)入门指南:从 Mock 类定义到期望验证
miniblink49 仓库中的 Google Mock(gmock)入门指南:从 Mock 类定义到期望验证 本文以 v8_7_5/testing/gmock
前端桌面应用Sinon `assert.threw` 完全指南:断言 fake、spy、stub 抛出异常的权威方法
Sinon assert.threw 完全指南:断言 fake、spy、stub 抛出异常的权威方法 assert.threw 是 Sinon 内置断言(Ass
测试开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考