news 2026/9/25 11:38:45

sinon sandbox.stub() 完全指南:非函数属性 Stubbing 与自动恢复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
sinon sandbox.stub() 完全指南:非函数属性 Stubbing 与自动恢复
  • 测试
  • 开发工具

【免费下载链接】sinon

Test spies, stubs and mocks for JavaScript.

项目地址:https://gitcode.com/gh_mirrors/si/sinon
点击查看免费下载

本文围绕 Sinon 沙箱(Sandbox)API 中的sandbox.stub()方法展开,讲解它与全局sinon.stub()完全一致的行为语义、在沙箱内的自动收集与恢复机制,以及最常被问到的非函数属性(non-function property)stubbing实战用法。读完本文,你将掌握如何在测试中通过sandbox.stub(obj, "prop").value(newVal)临时替换对象的普通数据属性(字符串、数字、对象等),并在测试结束时通过一次restore()自动还原现场,彻底告别手动清理。

sandbox.stub()是什么

根据 docs/concepts/sandboxes/api/stub.md 的官方定义:

sandbox.stub()与sinon.stub的用法完全一致。

这意味着你可以在沙箱上使用sinon.stub的全部能力,包括:

  • 包装已有函数:sandbox.stub(obj, "method")替换对象方法,且不会调用原始函数;
  • 预编程行为:通过.returns()、.throws()、.callsFake()、.resolves()、.rejects()、.yields()等方法定义返回值、抛错、回调等行为;
  • 按调用次数差异化响应:通过.onCall(n)让 stub 在连续调用时表现不同;
  • stub 非函数属性:通过.value(newVal)直接替换对象的普通数据属性。

两者唯一的差异在于生命周期管理:由沙箱创建的 stub 会被自动登记进沙箱的收集列表,调用sandbox.restore()时一次性还原,而全局sinon.stub()创建的 stub 需要手动调用stub.restore()或依赖默认沙箱的sinon.restore()。

默认沙箱:sinon 对象本身就是沙箱

从sinon@5.0.0起,sinon对象本身就是一个沙箱(即"默认沙箱"),它拥有与沙箱 API 完全相同的全部方法与属性(见 docs/concepts/sandboxes/index.md 与 sandbox API 索引)。

因此最推荐、最简洁的用法是直接调用sinon.stub()与sinon.restore(),无需显式创建沙箱。这一设计在 src/sinon/create-sandbox.js 的注释中有明确印证:

As of Sinon 5 the `sinon` instance itself is a Sandbox, so you hardly ever need to create additional instances for the sake of testing

自定义沙箱

当需要隔离的配置(如独立的 assert 选项、useFakeTimers、或injectInto注入目标)时,可以通过sinon.createSandbox(config)创建独立沙箱。createSandbox支持的关键配置项(定义于 src/sinon/create-sandbox.js):

  • properties:要暴露在沙箱上的 API 属性名列表,例如['spy', 'fake', 'restore'];
  • injectInto:将沙箱方法注入到指定对象中(主要用于集成场景,如 sinon-test);
  • useFakeTimers:是否默认启用假定时器,传对象时可携带定时器配置;
  • assertOptions:断言选项。

Stubbing 非函数属性:.value(newVal)

沙箱 stub 的核心扩展场景是替换非函数属性。普通stub(obj, "prop")只能替换函数类型的属性,而数据属性(字符串、数字、布尔值、对象等)需要通过.value()来定义新值。

完整可运行示例

以下示例直接取自仓库文档测试 docs/tests/docs/sandboxes/api/stub.test.js,展示了"stub 非函数属性 → 验证 → 沙箱恢复 → 验证还原"的完整闭环:

import t from "tap"; import sinon from "sinon"; t.test("sandbox.stub can stub non-function properties", (t) => { const sandbox = sinon.createSandbox(); const myObject = { hello: "world" }; // Stub the property sandbox.stub(myObject, "hello").value("Sinon"); // Verify the stub works t.equal(myObject.hello, "Sinon", "property should be stubbed to 'Sinon'"); // Restore via sandbox sandbox.restore(); // Verify restoration t.equal(myObject.hello, "world", "property should be restored to 'world'"); t.end(); });

执行流程拆解:

  1. sandbox.stub(myObject, "hello")在沙箱中登记 stub 并替换该属性;
  2. .value("Sinon")将属性值临时定义为"Sinon"(stub.value 文档 中定义该方法为"为 stub 定义一个新值");
  3. 断言确认myObject.hello === "Sinon";
  4. sandbox.restore()一次性还原沙箱内所有 stub;
  5. 断言确认属性恢复为原始值"world"。

使用默认沙箱的等价写法

如果不显式创建沙箱,直接使用默认沙箱即可,效果完全一致(见 docs/tests/docs/sandboxes/_index-1.test.js):

import tap from "tap"; import * as sinon from "sinon"; const myObject = { hello: "world" }; // 使用默认沙箱的 stub 方法 sinon.stub(myObject, "hello").value("Sinon"); tap.equal(myObject.hello, "Sinon", "property stubbed to Sinon"); sinon.restore(); tap.equal(myObject.hello, "world", "property restored to world");

局部恢复:调用stub.restore()

除了通过沙箱整体恢复,也可以只恢复单个 stub。.value()会返回 stub 本身,因此可以保留引用并单独调用其restore()方法(参见 docs/tests/docs/stubs/api/value-2.test.js):

const stub = sinon.stub(myObj, "example").value("newValue"); tap.equal(myObj.example, "newValue", "property has new value"); stub.restore(); tap.equal(myObj.example, "oldValue", "property restored to old value");

注意:sandbox.restore()与stub.restore()是有区别的——前者不带参数、一次性还原沙箱内全部 fake;后者只还原当前 stub。若误给sandbox.restore()传参,源码 src/sinon/sandbox.js 会抛出"sandbox.restore() does not take any parameters. Perhaps you meant stub.restore()"的明确提示。

源码视角:沙箱 stub 的底层机制

sandbox.stub的实现

在 src/sinon/sandbox.js 中,sandbox.stub的实现核心如下:

sandbox.stub = function () { // 将沙箱上下文作为首参传入,用于并行测试间隔离 callId const args = arrayProto.concat( [sandboxContext], arrayProto.slice(arguments), ); const createdStub = sinonStub.withContext.apply(sinonStub, args); const result = commonPostInitSetup(arguments, createdStub, true, /* ... */); addReturnedMethodsToCollection(result); return result; };

关键点:

  • 通过sinonStub.withContext传入sandboxContext,该上下文对象({ callId: 0 },见 src/sinon/sandbox.js)用于隔离并行测试之间的调用 ID 计数;
  • commonPostInitSetup会把创建的 stub 加入沙箱收集列表(addToCollection);
  • addReturnedMethodsToCollection还会把 stub 行为链返回的对象中自有方法一并收集,确保.onCall()产生的派生 stub 也能被统一还原。

非函数属性 stubbing 的底层判定

sinon.stub的入口实现在 src/sinon/stub.js 的stubImpl中,stub.withContext(context, object, property)最终都会落到它(src/sinon/stub.js)。当目标属性不是函数时,stub 不会去包装执行逻辑,而是直接返回一个可供.value()赋值的 stub 对象:

return isStubbingNonFuncProperty ? s : wrapMethod(object, property, s);

此外,stubImpl会对不存在的属性抛出明确错误(Cannot stub non-existent property ...),并对不可配置(non-configurable)或不可写(non-writable)的属性描述符进行校验(src/sinon/stub.js),提醒你检查属性描述符配置。

恢复机制的实现

sandbox.restore()的实现(src/sinon/sandbox.js)会按逆序遍历收集列表并逐个调用restore(),从而保证依赖顺序正确还原:

reverse(collection); applyOnEach(collection, "restore"); collection = [];

同时,沙箱还提供了细粒度的重置能力,全部作用于收集列表中的所有 fake(src/sinon/sandbox.js):

  • sandbox.reset():重置行为与历史;
  • sandbox.resetBehavior():仅重置行为;
  • sandbox.resetHistory():仅清空调用历史;
  • sandbox.verify():校验所有 mock 期望;
  • sandbox.verifyAndRestore():先verify()再restore(),若校验失败则抛出异常(src/sinon/sandbox.js)。

泄漏防护

沙箱内置了泄漏阈值保护(默认DEFAULT_LEAK_THRESHOLD = 10000,见 src/sinon/sandbox.js 与 src/sinon/sandbox.js):当收集的 fake 数量超过阈值且未恢复时,会打印警告提醒你在每个测试后restore。你可以通过修改沙箱的leakThreshold属性来调整或关闭该警告(详见 leak-threshold 文档)。

何时使用sandbox.stub()

结合 stubs 概念文档 的指引,在以下场景优先使用 stub(并通过沙箱管理生命周期):

  1. 控制方法行为以驱动特定代码路径:例如让方法抛出错误以测试错误处理逻辑,如stub.throws(new Error("I lost my pie :("));
  2. 阻止副作用方法被真实调用:例如替换fs.readFile以返回测试所需的假数据,如sinon.stub(fs, "readFile").callsFake(() => Promise.resolve("Apple pie"));
  3. 替换非函数数据属性:这是sandbox.stub().value()的专属能力,适合临时改写配置项、常量或状态字段;
  4. 按调用次数差异化响应:行为定义方法(如.returns()、.throws())多次调用会相互覆盖,此时用.onCall(n)指定每次调用的行为(on-call 文档)。

需要留意的是,Sinon 官方对新建代码推荐优先使用 fakes(如sinon.fake.returns()、sinon.fake.throws()),因其行为更简单、不可变且同样支持 spy API;但 stub 在onCall()差异化行为与属性 stubbing等高级场景中仍然不可或缺——这正是sandbox.stub().value()的价值所在。

小结

sandbox.stub()与sinon.stub()行为完全一致,但多了一层沙箱托管:所有由沙箱创建的 stub 会被自动收集,sandbox.restore()一次性完成还原。对于非函数属性,通过.value(newVal)可以轻松实现临时替换,并借助沙箱(或单个 stub 的restore())确保测试结束后原始值无损还原。无论是默认沙箱sinon.stub()还是sinon.createSandbox()自定义沙箱,这一模式都能让测试 teardown 变得更简洁、更不易出错。

  • 测试
  • 开发工具

【免费下载链接】sinon

Test spies, stubs and mocks for JavaScript.

项目地址:https://gitcode.com/gh_mirrors/si/sinon
点击查看免费下载
上一篇:BilibiliDown:轻松搞定B站视频下载,打造个人专属离线资源库
下一篇:如何彻底移除 ralph-claude-code:从零残留到干净系统的完整操作

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 11:37:47

xmlrpc.php 揭秘:WordPress 攻击面与防护加固指南

一个常见到让人麻木的场景:后台登录日志里一晚上多了几百条失败记录,服务器没有异常进程,CPU也正常,但带宽却在深夜被拉满。查了一圈,既不是后台密码泄露,也不是插件漏洞,最后在访问日志里发现一…

作者头像 李华
网站建设 2026/9/25 11:37:11

OpenClaw自定义skill环境变量传参:SKILL.md与metadata配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/25 11:35:06

Backspace长按失效?Windows与Linux键盘重复机制排查指南

1. 问题现象与核心影响范围Backspace 键长按不能连续删除、按一下只删一个字符,这个问题我前后遇到过不下十次,分布在 Windows 10、Windows 11、Windows Server 2016 以及几台 Ubuntu 和统信 UOS 机器上。表面上看是个小毛病,但它对日常操作效…

作者头像 李华