ARTICLE DETAIL

资讯详情

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

Sinon spy.alwaysThrew 完全指南:判断 spy/fake/stub 是否每次调用都抛出异常

Sinon spy.alwaysThrew 完全指南:判断 spy/fake/stub 是否每次调用都抛出异常 测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载spy.alwaysThrew是 Sinon 为 spy以及 fake、stub提供的断言式查询方法用于回答一个关键问题被测对象是否在每一次调用中都抛出了异常。与只关心至少抛过一次的spy.threw不同alwaysThrew要求所有历史调用全部抛错才返回true因此非常适合用来验证错误处理路径的稳定性——例如重试逻辑、幂等性测试或任何一次成功调用都算失败的严格场景。阅读本文后你将掌握alwaysThrew的调用形式、异常类型匹配规则、与threw的区别、底层实现原理以及三种重置方式。alwaysThrew是什么根据 官方 API 文档alwaysThrew是一个返回布尔值的方法Returnstrue, when the fake, spy or stub always threw an exception.其适用范围覆盖 Sinon 的三大核心测试替身fake通过sinon.fake创建spy通过sinon.spy或sinon.spy(object, method)创建stub通过sinon.stub创建也就是说只要对象具备 Sinon 代理proxy能力spy.alwaysThrew就可用其行为在三种替身间完全一致。与spy.threw的区别方法语义判定条件spy.threw()至少抛过一次异常任一调用抛出异常即返回truespy.alwaysThrew()每次都抛出了异常所有调用都抛出异常才返回true用一句话记忆threw是存在性判断matchAny truealwaysThrew是全称性判断matchAny false。如果 spy 只被调用过一次两者结果相同一旦 spy 被调用多次只有每次都抛错时alwaysThrew才为true。基本用法最简单的形式是不传任何参数直接判断 spy 是否逢调必抛const f sinon.fake.throws(new Error(oh dear)); try { f(); } catch (e) { // 捕获异常让调用正常记账 } try { f(); } catch (e) { // 第二次调用同样抛错 } console.log(f.alwaysThrew()); // true注意被sinon.fake.throws包装的函数每次调用都会抛出异常因此在测试代码中调用它时必须用try/catch或测试框架的assert.throws之类断言包裹否则异常会直接中断测试进程。这一点在 文档配套测试用例 中有明确示范。混合场景并非每次抛错时返回falsealwaysThrew的命名强调always所以只要出现过一次正常返回就立刻失效let callCount 0; const spy sinon.spy(function () { callCount 1; if (callCount 1) { throw new Error(第一次调用抛错); } // 第二次调用正常返回 }); try { spy(); } catch (e) { // 第一次抛错 } spy(); // 第二次正常返回 console.log(spy.alwaysThrew()); // false因为第二次没有抛错这一行为对应源码测试 spy-test.js 中的用例 returns false if some calls did not throw。spy 一次都没被调用时当 spy 从未被调用时alwaysThrew()返回false见 spy-test.js。这与delegateToCalls的默认逻辑一致——没有调用记录自然谈不上每次调用都抛错。按异常类型匹配alwaysThrew(TypeError)alwaysThrew支持传入一个参数用于限定异常类型只有当每一次调用抛出的异常都匹配该类型时才返回trueconst f sinon.fake.throws(new TypeError(a specific error)); try { f(); } catch (e) { // 捕获 } try { f(); } catch (e) { // 捕获 } f.alwaysThrew(TypeError); // true每次抛的都是 TypeError f.alwaysThrew(ArgumentError); // false类型不匹配从源码看类型匹配的实现位于 proxy-call.js 的threw方法threw: function threw(error) { if (typeof error undefined || !this.exception) { return Boolean(this.exception); } return this.exception error || this.exception.name error; },匹配规则有两条路径对象引用相等this.exception error——传入的实参对象与调用实际抛出的异常对象是同一个引用错误类型名相等this.exception.name error——传入字符串与error.name属性一致例如TypeError、RangeError、Error。因此你可以传入具体的异常实例也可以传入构造器名字符串。注意第二条规则是按name精确匹配的抛TypeError的 spyalwaysThrew(Error)会返回false见 spy-test.js因为TypeError.name TypeError而非Error。传参形式的语义是每次调用的异常都匹配该类型。也就是说即使每次抛出的异常实例不同只要name一致alwaysThrew(TypeError)仍为true。断言辅助assert.alwaysThrew除了在 spy 上直接调用Sinon 的断言模块还提供了对应的辅助方法assert.alwaysThrew(spy[, exception])失败时抛出带语义化消息的异常。其实现位于 assert.jsmirrorPropAsAssertion(alwaysThrew, %n did not always throw exception%C);使用示例const spy sinon.spy(function () { throw new Error(boom); }); try { spy(); } catch (e) { // 捕获 } sinon.assert.alwaysThrew(spy); // 通过 sinon.assert.alwaysThrew(spy, TypeError); // 失败抛出断言异常失败信息模板中的%n会被替换为 spy 的名称%C会被替换为调用上下文信息方便快速定位是哪个替身、哪次调用出了问题。底层实现原理alwaysThrew并不是一个独立手写的函数而是通过 Sinon 的delegateToCalls机制自动生成的。在 proxy.js 中有这样两行声明delegateToCalls(proxyApi, threw, true); delegateToCalls(proxyApi, alwaysThrew, false, threw);这里的关键是delegateToCalls的第二个参数matchAnythrew使用matchAny true任一调用满足条件即返回truealwaysThrew使用matchAny false并把实际委托方法指向threw所有调用都必须满足才返回true。delegateToCalls的完整逻辑在 proxy-call-util.jsproxy[method] function () { if (!this.called) { // 从未被调用默认返回 false除非提供了 notCalled 回调 return false; } // ...遍历所有调用 for (let i 0, l this.callCount; i l; i 1) { currentCall this.getCall(i); const returnValue currentCall[actual || method].apply( currentCall, arguments, ); if (returnValue) { matches 1; if (matchAny) { return true; // threw一有匹配立即返回 } } } return matches this.callCount; // alwaysThrew必须全部匹配 };所以alwaysThrew()的求值过程可以概括为对 spy 的每一次调用call 对象分别执行threw(...)统计匹配次数若匹配次数等于总调用次数则返回true。这就是every-call语义在实现层面的体现。一个值得注意的边界行为如果 spy从未被调用delegateToCalls会直接返回false源码 proxy-call-util.js因此alwaysThrew对零调用场景的答案是确定的false不会出现空真vacuous truth式的歧义。将alwaysThrew重置为默认值alwaysThrew的值取决于 spy 的历史调用记录因此清除调用历史即可将其复位为默认的false。官方文档给出了三种等价途径实例级spy.resetHistory()——只重置这一个 spy 的历史记录保留替身本身全局级sinon.resetHistory()——重置所有已创建的 spy/stub 的历史记录全局级sinon.reset()——更彻底同时重置历史记录与行为behavior。示例与 文档测试 中的复位断言对应const f sinon.fake.throws(new Error(oh dear)); try { f(); } catch (e) { // 捕获 } console.log(f.alwaysThrew()); // true sinon.reset(); console.log(f.alwaysThrew()); // false历史已清空在测试框架如 Mocha、Jest、tap中通常把sinon.reset()或sinon.resetHistory()放进afterEach/beforeEach钩子保证每个用例之间互不污染。实战场景示例场景一验证错误处理分支稳定抛错假设被测函数unreliableApi在特定配置下应当始终失败可以用alwaysThrew直接断言const stub sinon.stub(api, call).throws(new RangeError(out of range)); // 触发多次调用每次都被业务层捕获 for (let i 0; i 3; i) { try { api.call(); } catch (e) { // 业务层已处理 } } stub.alwaysThrew(RangeError); // true场景二区分至少失败一次与每次都失败回归测试中常需要区分两类 bug偶发失败threw为true与必然失败alwaysThrew为true。对偶发问题的修复验证可用threw确认问题出现过对接口完全不工作的阻断性问题则必须用alwaysThrew确认没有任何一次调用成功过。场景三与assert断言配合的失败信息在 CI 中sinon.assert.alwaysThrew(spy, TypeError)失败时抛出的异常会带上 spy 名称与调用上下文%n did not always throw exception%C比裸写if (!spy.alwaysThrew()) throw ...更便于定位问题。小结spy.alwaysThrew()返回true当且仅当 spy/fake/stub每一次调用都抛出了异常适用于 fake、spy、stub 三类替身无参形式判断是否逢调必抛传参形式alwaysThrew(TypeError)或传异常实例判断每次抛出的异常是否都匹配指定类型与spy.threw的区别在于全称量化语义实现上由 proxy.js 的delegateToCalls(proxyApi, alwaysThrew, false, threw)保证类型匹配基于异常对象引用或error.name字符串精确匹配见 proxy-call.js从未被调用的 spy 返回false通过spy.resetHistory、sinon.resetHistory或sinon.reset三种方式复位为默认值断言辅助函数sinon.assert.alwaysThrew提供带上下文信息的失败消息见 assert.js。延伸阅读与alwaysThrew配套的always系列还包括spy.alwaysCalledOn、spy.alwaysCalledWith、spy.alwaysCalledWithExactly、spy.alwaysCalledWithMatch与spy.alwaysReturned它们共享相同的delegateToCalls全称量化机制只是委托的具体 call 方法不同。赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐深入解析 Sinon 的 assert.alwaysThrew验证 spy 是否每次都抛出异常深入解析 Sinon 的 assert.alwaysThrew 验证 spy 是否每次都抛出异常 Sinon 是 JavaScript 生态中最常用的测试替身测试开发工具Sinon assert.threw 完全指南断言 fake、spy、stub 抛出异常的权威方法Sinon assert.threw 完全指南断言 fake、spy、stub 抛出异常的权威方法 assert.threw 是 Sinon 内置断言Ass测试开发工具Sinon assert.alwaysCalledWithExactly校验 fake/spy/stub 每次调用参数都完全一致Sinon assert.alwaysCalledWithExactly 校验 fake/spy/stub 每次调用参数都完全一致 导读 assert.alw测试开发工具上一篇sealed-secrets控制器资源限制防止资源滥用的配置下一篇在 Neovim 中接入 DeepSeekminuet-ai.nvim 智能代码补全配置实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表