- 测试
- 开发工具
【免费下载链接】sinon
Test spies, stubs and mocks for JavaScript.
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(); });这个用例揭示了三个要点:
- 初始状态:新建的匿名 stub(
sinon.stub())called为false; - 调用后:执行一次
stub()后,called变为true; - 重置后:调用
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; },从源码结构可以梳理出三个值得注意的实现事实:
重置的字段清单:它一次性清空的不只是
called和callCount,还包括所有与"调用过程"相关的记录——args(每次调用的参数数组)、thisValues(每次调用的this上下文)、returnValues(返回值)、exceptions(抛出的异常)、callIds(调用顺序编号)以及firstCall/secondCall/thirdCall/lastCall等便捷引用。也就是说,calledWith、calledOn、returned、threw等基于历史数据的断言,在重置后都会回到"从未发生"的状态。递归重置派生 fake:如果该 stub 通过
withArgs(...)创建了按参数匹配的派生 stub(这些派生 fake 存放在this.fakes中),resetHistory()会递归地对每个派生 fake 同样执行resetHistory()。这保证了"主 stub + 其withArgs分支"的整体历史被一致性清空,不会出现主桩已重置而分支计数残留的错乱。禁止在调用过程中重置:如果
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); } } };从源码可以看出两个细节:
sandbox.resetHistory()遍历沙箱的collection(所有已创建的 fake/spy/stub),对每个成员调用其resetHistory;对于不支持resetHistory的成员,则退化为调用reset。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);六、实践建议与注意事项
- 区分"历史"与"行为"的使用时机:当你在
beforeEach或测试中途需要"清零计数重新验证",但又不想重新配置returns/throws等行为时,resetHistory是唯一正确的选择;如果误用reset(),行为配置也会丢失,stub 会退化回返回undefined。 withArgs分支同步重置:由于实现会递归重置fakes,带参数匹配的派生 stub 历史也会一并清空,无需手动逐个清理。- 不要在回调内部重置:任何在 stub 执行过程中(
invoking状态)调用resetHistory的写法都会抛出InvalidResetException,请把重置移到调用完成之后。 - 偏好沙箱级 API:在
sinon.createSandbox()场景下,优先使用sandbox.resetHistory()或sandbox.reset()做统一清理,再配合sandbox.restore()在测试结束后还原被替换的原方法,避免手工维护零散的 stub 引用。 - 文档对照:完整的 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.
相关推荐
Sinon sandbox.resetBehavior 完全指南:批量重置 stub 行为而保留调用历史
Sinon sandbox.resetBehavior 完全指南:批量重置 stub 行为而保留调用历史 sandbox.resetBehavior 是 Sin
测试开发工具Bokeh 数学符号渲染完全指南:在图表与控件中使用 LaTeX 和 MathML
Bokeh 数学符号渲染完全指南:在图表与控件中使用 LaTeX 和 MathML Bokeh 原生支持在图表中渲染数学公式,允许开发者使用 LaTeX 与 M
测试开发工具sinon sandbox.reset() 详解:重置 Stub 可变行为与 Fake/Spy/Stub 调用历史
sinon sandbox.reset 详解:重置 Stub 可变行为与 Fake/Spy/Stub 调用历史 导读 sandbox.reset 是 Sinon
测试开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考