news 2026/9/25 11:36:14

sinon 中 stub.resetHistory() 的完整指南:重置调用历史而不影响桩行为

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
sinon 中 stub.resetHistory() 的完整指南:重置调用历史而不影响桩行为
  • 测试
  • 开发工具

【免费下载链接】sinon

Test spies, stubs and mocks for JavaScript.

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

stub.resetHistory()是 Sinon.JS 为 stub(桩函数)提供的核心维护方法,用于清空一个 stub 自创建以来的全部调用历史记录(call history),而不改变该 stub 已经配置好的行为(behavior)。在单元测试中,它通常用于同一测试文件内的多阶段场景——例如先验证某次调用,再重置计数,继续验证下一轮调用,避免反复重建 stub 或沙箱。读完本文,你将掌握stub.resetHistory()的语义边界、底层实现原理、与stub.reset()/stub.resetBehavior()的区别,以及如何通过sinon.resetHistory()批量重置整个沙箱内所有 fake、spy 和 stub 的历史。

一、API 定位:它属于 Stub API 的"重置三兄弟"之一

在 Sinon.JS 的官方 Stub API 中(见 docs/concepts/stubs/api/index.md 的 Methods 列表),与"重置"相关的 API 共有三个,它们覆盖了 stub 生命周期的不同维度:

API作用是否影响行为(behavior)是否影响历史(history)
stub.resetHistory()仅重置调用历史❌ 不影响✅ 重置
stub.resetBehavior()重置为默认行为(返回undefined)✅ 重置❌ 不影响
stub.reset()同时重置行为与历史✅ 重置✅ 重置

其中,stub.reset()在官方文档中被明确描述为"equivalent to calling bothstub.resetBehavior()andstub.resetHistory()"(等价于依次调用这两个方法),见 docs/concepts/stubs/api/reset.md。因此,理解resetHistory的关键,就在于把它和"行为"严格区分开:历史是"它被怎么调用了",行为是"它被调用后返回什么/做什么"。

二、基本用法与官方测试用例

官方为stub.resetHistory()提供的测试位于 docs/tests/docs/stubs/api/reset-history.test.js,它直接演示了该 API 的完整语义:

import tap from "tap"; import * as sinon from "sinon"; tap.test("stub.resetHistory", (t) => { const stub = sinon.stub(); t.notOk(stub.called, "stub.called is false initially"); stub(); t.ok(stub.called, "stub.called is true after call"); stub.resetHistory(); t.notOk(stub.called, "stub.called is false after resetHistory"); t.end(); });

这个用例揭示了三个要点:

  1. 初始状态:新建的匿名 stub(sinon.stub())called为false;
  2. 调用后:执行一次stub()后,called变为true;
  3. 重置后:调用stub.resetHistory()后,called又回到false,仿佛从未被调用过。

典型应用场景是在同一个it/test内做"调用计数清零",例如:

const spy = sinon.spy(api, "fetchData"); // 第一轮验证 api.fetchData("/a"); assert(spy.calledOnce); // 重置历史,开始第二轮验证 spy.resetHistory(); api.fetchData("/b"); api.fetchData("/c"); assert(spy.calledTwice); // 计数从零重新开始,不受第一轮影响

注意:resetHistory是 spy 与 stub 共有的能力(stub 本身继承自 spy),因此上述写法对两者均适用。

三、底层实现:resetHistory 到底清空了哪些字段

要准确使用这个 API,必须知道"历史"在实现层面究竟包含什么。resetHistory的实际实现位于 src/sinon/proxy.js(stub、spy、fake 共同继承自 proxy 的 API):

resetHistory: function () { if (this.invoking) { const err = new Error( "Cannot reset Sinon function while invoking it. " + "Move the call to .resetHistory outside of the callback.", ); err.name = "InvalidResetException"; throw err; } this.called = false; this.notCalled = true; this.calledOnce = false; this.calledTwice = false; this.calledThrice = false; this.callCount = 0; this.firstCall = null; this.secondCall = null; this.thirdCall = null; this.lastCall = null; this.lastArg = null; this.args = []; this.firstArg = null; this.returnValues = []; this.thisValues = []; this.exceptions = []; this.callIds = []; this.errorsWithCallStack = []; if (this.fakes) { forEach(this.fakes, function (fake) { fake.resetHistory(); }); } return this; },

从源码结构可以梳理出三个值得注意的实现事实:

  1. 重置的字段清单:它一次性清空的不只是called和callCount,还包括所有与"调用过程"相关的记录——args(每次调用的参数数组)、thisValues(每次调用的this上下文)、returnValues(返回值)、exceptions(抛出的异常)、callIds(调用顺序编号)以及firstCall/secondCall/thirdCall/lastCall等便捷引用。也就是说,calledWith、calledOn、returned、threw等基于历史数据的断言,在重置后都会回到"从未发生"的状态。

  2. 递归重置派生 fake:如果该 stub 通过withArgs(...)创建了按参数匹配的派生 stub(这些派生 fake 存放在this.fakes中),resetHistory()会递归地对每个派生 fake 同样执行resetHistory()。这保证了"主 stub + 其withArgs分支"的整体历史被一致性清空,不会出现主桩已重置而分支计数残留的错乱。

  3. 禁止在调用过程中重置:如果resetHistory在 stub 自身的回调执行期间被调用(this.invoking为真),会抛出InvalidResetException错误,提示把重置调用移到回调之外。这是为了防止在调用栈进行中清空记录导致内部状态不一致。

此外,resetHistory()返回this(stub 本身),因此可以链式调用,例如stub.resetHistory().returns(42)在重置历史后立即配置新行为。

四、与 stub.reset() 的关系:组合即reset()

stub.reset()的实现位于 src/sinon/stub.js:

reset: function () { this.resetHistory(); this.resetBehavior(); },

可以看到reset()本质上就是resetHistory()与resetBehavior()的顺序组合。而resetBehavior()(src/sinon/stub.js)做的则是另一件事:清空defaultBehavior、behaviors数组,删除returnValue、returnArgAt、fakeFn等行为字段,把returnThis/resolveThis置回false,并同样递归处理withArgs派生 fake。它的效果是让 stub 回到"未配置任何行为"的默认状态——此时调用 stub 将返回undefined(对应测试 docs/tests/docs/stubs/api/reset-behavior.test.js 中的断言)。

因此:

  • 只想重新统计调用次数,保留返回值/抛出异常等行为配置→ 用stub.resetHistory();
  • 只想撤掉行为配置,但保留已发生的调用记录→ 用stub.resetBehavior();
  • 两者都要恢复出厂状态→ 直接用stub.reset(),或依次调用两个方法。

三者的官方对照说明分别见 docs/concepts/stubs/api/reset-history.md、docs/concepts/stubs/api/reset-behavior.md 与 docs/concepts/stubs/api/reset.md。

五、沙箱级批量重置:sinon.resetHistory() 与 sandbox.resetHistory()

resetHistory不仅存在于单个 stub 上,Sinon 还提供了沙箱(sandbox)级别的批量版本。官方文档 docs/concepts/sandboxes/api/reset-history.md 说明:sandbox.resetHistory()会重置使用该沙箱创建的所有 fakes、spies 和 stubs的历史。由于 Sinon 根对象本身就是一个默认沙箱,所以也可以直接调用sinon.resetHistory()。

其实现位于 src/sinon/sandbox.js:

sandbox.reset = function reset() { applyOnEach(collection, "reset"); applyOnEach(collection, "resetHistory"); }; sandbox.resetHistory = function resetHistory() { for (let i = 0; i < collection.length; i++) { const f = collection[i]; const method = f.resetHistory || f.reset; if (typeof method === "function") { method.call(f); } } };

从源码可以看出两个细节:

  1. sandbox.resetHistory()遍历沙箱的collection(所有已创建的 fake/spy/stub),对每个成员调用其resetHistory;对于不支持resetHistory的成员,则退化为调用reset。
  2. sandbox.reset()则会先对所有成员执行reset(清空行为),再执行resetHistory(清空历史)——这正好与单个 stub 上reset()的"组合语义"保持一致。

沙箱级的历史重置测试见 docs/tests/docs/sandboxes/api/reset-history.test.js,其验证路径与单个 stub 的测试完全对称:创建 fake → 调用 → 断言called为真 →sinon.resetHistory()→ 断言called回到假。

使用沙箱批量重置的典型写法:

const sandbox = sinon.createSandbox(); const stubA = sandbox.stub(api, "methodA"); const stubB = sandbox.stub(api, "methodB"); api.methodA(); api.methodB(); assert(sandbox.assert.calledOnce(stubA)); // 一次调用清空沙箱内所有成员的历史 sandbox.resetHistory(); assert(!stubA.called && !stubB.called);

六、实践建议与注意事项

  1. 区分"历史"与"行为"的使用时机:当你在beforeEach或测试中途需要"清零计数重新验证",但又不想重新配置returns/throws等行为时,resetHistory是唯一正确的选择;如果误用reset(),行为配置也会丢失,stub 会退化回返回undefined。
  2. withArgs分支同步重置:由于实现会递归重置fakes,带参数匹配的派生 stub 历史也会一并清空,无需手动逐个清理。
  3. 不要在回调内部重置:任何在 stub 执行过程中(invoking状态)调用resetHistory的写法都会抛出InvalidResetException,请把重置移到调用完成之后。
  4. 偏好沙箱级 API:在sinon.createSandbox()场景下,优先使用sandbox.resetHistory()或sandbox.reset()做统一清理,再配合sandbox.restore()在测试结束后还原被替换的原方法,避免手工维护零散的 stub 引用。
  5. 文档对照:完整的 Stub API 方法清单与各 API 的单独文档,可在 docs/concepts/stubs/api/index.md 及其同目录下的reset.md、reset-behavior.md、reset-history.md中逐一查阅。

小结

stub.resetHistory()是 Sinon.JS 中"精准重置"能力的代表:它只清空调用历史(called、callCount、args、returnValues、thisValues、exceptions、callIds等全部记录),保留已配置的行为,并递归作用于withArgs派生 fake;在实现层面它由 proxy 统一提供(src/sinon/proxy.js),在沙箱层面则由 src/sinon/sandbox.js 的resetHistory批量委托。配合stub.resetBehavior()(行为维度)与stub.reset()(组合维度),你可以精确控制测试中每一个 stub 的状态生命周期,写出更清晰、更可控的单元测试。

  • 测试
  • 开发工具

【免费下载链接】sinon

Test spies, stubs and mocks for JavaScript.

项目地址:https://gitcode.com/gh_mirrors/si/sinon
点击查看免费下载
上一篇:BullMQ 快速上手指南:从安装、生产任务到 Worker 消费与事件监听
下一篇:PinWin窗口置顶工具:3分钟掌握多窗口高效管理的终极秘诀

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

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

Atlas 300V部署YOLO全流程:环境配置、模型转换与性能调优

如果你最近在搜“atlas部署yolo”&#xff0c;那你大概率是刚拿到一块Atlas加速卡、一台边缘小站&#xff0c;或者在帮客户把目标检测模型从GPU往昇腾平台上迁移。我今年做了两个类似的落地项目&#xff0c;踩了不少坑&#xff0c;也整理出一套可以照着抄的流程。今天这篇就围绕…

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

在树莓派5上运行codex-desktop-linux:arm64部署完整实践指南

在树莓派5上运行codex-desktop-linux&#xff1a;arm64部署完整实践指南 【免费下载链接】codex-desktop-linux Unofficial ChatGPT desktop app for Linux (formerly the Codex app), built locally from OpenAI’s official macOS app. Includes Chat, Work, and Codex. Pack…

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

嵌入式开发提效实战:CMake+模块化驱动+量产交付链路

1. 这不是营销话术&#xff0c;而是真实存在的效率跃迁“嵌入式开发者的福音”——这标题乍看像某篇公众号推文的夸张标题党&#xff0c;但如果你正在用STM32写裸机驱动、在RTOS里反复调试任务调度延迟、为一个SPI时序偏差200ns而抓耳挠腮、或者刚被客户临时要求把原本跑FreeRT…

作者头像 李华