ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Sinon 中 mock.restore 完全指南:还原所有被 Mock 的方法及其底层实现原理

Sinon 中 mock.restore 完全指南:还原所有被 Mock 的方法及其底层实现原理 测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载mock.restore()是 Sinon 中Mock对象提供的关键方法用于将对象上所有被 Mock 的方法还原为原始实现。本文以docs/concepts/mocks/api/restore.md为核心结合仓库中的测试用例docs/tests/docs/mocks/api/restore.test.js与源码实现src/sinon/mock.js、src/sinon/util/core/wrap-method.js展开帮助你掌握mock.restore()的调用方式、验证机制、与verify()/沙箱的协作关系以及它在测试生命周期中扮演的角色。一、mock.restore是什么在 Sinon 的 Mock API 中mock.restore()的作用非常明确——还原Restore所有被 Mock 的方法。当你通过sinon.mock(obj)创建一个 mock 对象并通过mock.expects(methodName)为对象的方法设置预编程期望expectation后Sinon 会用一个代理函数**替换wrap**原对象上的真实方法。mock.restore()就是用来撤销这一替换动作的它遍历 mock 对象记录的所有代理方法调用每个代理方法自带的restore函数把对象属性还原回最初的原始实现。mock.restore();这是 Mock API 三大核心方法之一。根据docs/concepts/mocks/api/_index.mdmock 对象主要提供expects——为指定方法创建期望restore——还原所有被 Mock 的方法verify——校验所有期望是否被满足并自动还原。二、基本用法与完整示例1. 来自官方文档的核心描述官方文档docs/concepts/mocks/api/restore.md对它的描述只有一句话Restores all mocked methods.还原所有被 Mock 的方法。但这背后蕴含的测试生命周期语义需要结合仓库中的测试用例来完整理解。2. 官方测试用例逐行解读仓库中为mock.restore提供了专门的测试文件docs/tests/docs/mocks/api/restore.test.js完整展示了它的使用场景import tap from tap; import * as sinon from sinon; tap.test(mock.restore - restores all mocked methods, (t) { const obj { greet: function (name) { return Hello ${name}; } }; const mock sinon.mock(obj); const expectation mock.expects(greet); obj.greet(Mickey Mouse); // mocked methods have a restore method on them t.equal( typeof obj.greet.restore, function, mocked method has restore function ); mock.restore(); // the original greet method has been restored t.equal( typeof obj.greet.restore, undefined, restored method no longer has restore function ); t.end(); });这段测试揭示了三个关键事实Mock 过程会替换原方法mock.expects(greet)之后obj.greet不再是原始函数而是一个被包装wrapped的代理方法并且该代理方法上额外挂载了一个restore函数属性typeof obj.greet.restore function。mock.restore()还原全部方法调用mock.restore()后obj.greet被还原为原始实现其上的restore属性也随之消失typeof obj.greet.restore undefined说明对象属性已恢复到 Mock 之前的真实状态。调用方式无需传参mock.restore()不需要任何参数会自动处理该 mock 对象上所有被expects过的代理方法。3. 一个可运行的完整示例结合上面测试的写法在真实测试套件中这里以 tap 为例也适用于 Mocha、Jest 等任意测试框架可以这样使用import * as sinon from sinon; test(myService 在被 mock 后可以完整还原, () { const service { fetch: async () real-data, save: () saved }; const mock sinon.mock(service); mock.expects(fetch).resolves(mock-data); mock.expects(save).once(); // ... 执行业务逻辑触发被 mock 的方法 ... mock.verify(); // 校验期望是否被满足同时也会还原方法 // 或者手动mock.restore(); // 只还原方法不做校验 });三、底层实现mock.restore在源码中做了什么1. 核心实现位于src/sinon/mock.jsmock.restore的实现非常简洁位于 src/sinon/mock.jsrestore: function restore() { const object this.object; each(this.proxies, function (proxy) { if (typeof object[proxy].restore function) { object[proxy].restore(); } }); },逐行分析其工作流程this.object即sinon.mock(obj)传入的原始对象创建时通过extend.nonEnum({}, mock, { object: object })挂载在 mock 对象上见 mock.create。this.proxies记录所有被expects过的方法名数组。在 mock.expects 中第一次为某个方法创建期望时会执行wrapMethod(this.object, method, ...)包装方法并把方法名push(this.proxies, method)记录下来。逐代理还原遍历proxies对每个方法名先判断object[proxy].restore是否为函数——也就是前文测试中断言的“mocked method has restore function”——然后调用它从而把方法还原为原始实现。2.restore属性从哪来wrap-method.js被 mock 的方法上为什么会自带一个restore函数答案在src/sinon/util/core/wrap-method.js。该方法负责将对象属性替换为代理并在代理上通过extend.nonEnum挂载了以下元数据见 wrap-method.jsextend.nonEnum(target, { displayName: property, wrappedMethod: wrappedMethods[i], // 保存原始方法的引用 stackTraceError: new Error(Stack Trace for original), restore: restore, // 每个被包装的方法都自带 restore }); target.restore.sinon true;关键点wrappedMethod被包装前原始方法的引用被保存下来这正是restore能够“回到过去”的凭据。restore函数的还原策略针对不同的对象属性形态自有属性 vs 原型链属性、访问器 get/set vs 普通数据属性、ES5 支持与否wrap-method.js内部实现了对应的还原逻辑——能delete就delete原型属性场景否则用Object.defineProperty写回原始 descriptor或直接赋值回wrappedMethod见 wrap-method.js。因此mock.restore()并非简单赋值而是“尽力恢复原始属性描述符”的精细化操作。重复包装防护restore.sinon true标记用于检测“Attempted to wrap ... which is already wrapped”避免对同一方法进行二次包装导致还原链混乱。3. 匿名 Mock 的特殊情形mock(object)的源码mock.js支持两种入参export default function mock(object) { if (!object || typeof object string) { return mockExpectation.create(object ? object : Anonymous mock); } return mock.create(object); }传入对象走mock.create(object)返回一个带expects/restore/verify方法的 mock 对象restore按上述逻辑还原对象方法不传对象或传入字符串名称返回一个匿名 mockAnonymous mock本质上是独立的期望函数没有对象方法可还原也就无需也无法调用mock.restore()。四、mock.restore与mock.verify的关系理解mock.restore的一个重要维度是它与mock.verify的协作。从 mock.js 中的 verify 实现 可以看到verify: function verify() { const expectations this.expectations || {}; const messages this.failures ? slice(this.failures) : []; const met []; each(this.proxies, function (proxy) { each(expectations[proxy], function (expectation) { if (!expectation.met()) { push(messages, String(expectation)); } else { push(met, String(expectation)); } }); }); this.restore(); // 关键verify 内部也会调用 restore if (messages.length 0) { mockExpectation.fail(join(concat(messages, met), \n)); } else if (met.length 0) { mockExpectation.pass(join(concat(messages, met), \n)); } return true; },由此可以得出一个对测试编写至关重要的结论mock.verify()在校验完所有期望后会自动调用this.restore()还原所有被 mock 的方法然后才根据期望满足情况pass/fail。这一点在测试文件docs/tests/docs/mocks/api/verify-1.test.js中得到了印证——该测试在mock.verify()之后断言typeof obj.greet.restore undefined方法已被还原。因此如果你只关心期望校验调用mock.verify()即可它会顺手完成还原如果你想在不校验或稍后校验的情况下先释放对象可以单独调用mock.restore()重复调用是安全的restore遍历proxies时会先判断typeof object[proxy].restore function已被还原的方法不再满足该条件会被静默跳过不会抛错。五、与沙箱sandbox的配合在大型测试套件中更常见的做法是使用 Sinon 沙箱统一管理生命周期。沙箱的restore文档docs/concepts/sandboxes/api/restore.md指出sandbox.restore()还原所有通过沙箱创建的fakes、spies和stubs。沙箱体系与mock.restore是互补的两条路径手动管理sinon.mock(obj)创建独立 mock测试结束时手动调用mock.restore()或mock.verify()触发隐式还原沙箱管理使用sinon.createSandbox()统一创建 spy/stub/mock由sandbox.restore()在afterEach等钩子中统一还原避免遗忘导致的测试间状态泄漏。具体沙箱用法可参考 docs/concepts/sandboxes/create-sandbox.md 与 docs/concepts/sandboxes/api/restore.md。六、最佳实践与注意事项每个测试至多一个 mockdocs/concepts/mocks/index.md明确建议mock 只应作用于“被测方法”本身一般单个测试中不要超过一个 mock可含多个 expectation过度使用会强制测试实现细节。若你只想替换行为而不做前置期望应改用 stubs或参考 fakes 配合显式断言。及时还原防止泄漏被 mock 的方法不还原会污染后续测试尤其是afterEach中未清理时。优先在afterEach中调用mock.restore()或直接依赖mock.verify()的隐式还原。验证“还原”本身如官方测试所示可以通过typeof obj.method.restore undefined断言方法确已还原也可以直接调用原方法检查返回值是否恢复真实行为。不要对同一方法重复 mockwrap-method.js的checkWrappedMethod会检测“already wrapped/stubbed/spied on”并抛出TypeError若先mock.restore()再重新expects则是安全的。匿名 mock 没有 restore 语义sinon.mock(name)返回的是独立期望函数不属于“还原对象方法”的范畴不需要也无法调用mock.restore()。七、结语mock.restore()虽然 API 表面只有一句话——“Restores all mocked methods”——但它在测试生命周期中承担着“安全着陆”的关键职责从mock.expects的包装替换到wrap-method.js中对原始方法引用wrappedMethod的保存与精细化还原再到mock.verify()内部隐式调用restore整条链路共同保证了 Mock 不会把测试状态泄漏到测试之外。掌握它你就能安全、放心地使用 Sinon 的 Mock 能力编写前置期望式测试。相关资源索引官方文档docs/concepts/mocks/api/restore.md、docs/concepts/mocks/api/_index.md官方测试docs/tests/docs/mocks/api/restore.test.js、docs/tests/docs/mocks/api/verify-1.test.js核心源码src/sinon/mock.js、src/sinon/util/core/wrap-method.js关联概念Mocks 总览、Expectations、沙箱 restore赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐Jest ES6 Class Mocks 完全指南4 种 Mock 方式与底层原理剖析Jest ES6 Class Mocks 完全指南4 种 Mock 方式与底层原理剖析 ES6 类本质上只是带语法糖的构造函数因此对 ES6 类的 Mock测试质量保障代码覆盖率开发工具Sinon Sandbox 中的 mock 方法在沙箱内创建与管理 Mock 的完整指南Sinon Sandbox 中的 mock 方法在沙箱内创建与管理 Mock 的完整指南 导读 本文以 docs/concepts/sandboxes/api测试开发工具Cytoscape.js 元素动画完全指南eles.animate() 的 API 用法与底层实现原理Cytoscape.js 元素动画完全指南eles.animate 的 API 用法与底层实现原理 本文聚焦 Cytoscape.js 图库中 元素集合co数据可视化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表