测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载Sinon 是 JavaScript 生态中最常用的测试替身test double库之一其内置的sinon.assert断言体系将fake、spy、stub的行为验证从手动判断布尔值升级为带详细错误信息的断言失败。本文聚焦其中专门用于异常行为的断言方法assert.alwaysThrew它校验被观测对象每一次调用是否都抛出了指定异常。读完本文你将掌握alwaysThrew的完整签名、参数语义、通过/失败条件、与assert.threw的差异以及它在 Sinon 源码中的底层实现原理能够直接将其应用到自己的测试代码中。一、assert.alwaysThrew(spy, exception)的语义alwaysThrew是sinon.assert断言家族的一员其行为与alwaysCalledWith等 always 系列一致不关心某一次调用是否满足条件而是要求全部调用都满足。签名sinon.assert.alwaysThrew(spy[, exception]);通过条件当fake、spy或stub的每一次调用都抛出给定的异常时断言通过。exception参数的两种合法形式形式说明匹配逻辑String异常类型名例如TypeError、RangeError与抛出的异常对象的name属性比对实际对象异常实例例如new TypeError(not an apple pie)与抛出的异常对象做严格引用比较省略exception参数当只传入一个参数时断言退化为spy 是否每次都抛出了任意异常——只要所有调用都抛出异常即通过不关心具体类型或实例。从实现角度exception的匹配逻辑定义在 src/sinon/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; },可以看到未传error时仅判断该次调用是否抛出了异常传入后则分别支持引用相等对象与name属性相等字符串类型名两种比对方式。注意它是先比较引用this.exception error再比较namethis.exception.name error因此即使传入一个与抛出异常同类型的字符串名也能正确匹配。二、通过条件与失败条件alwaysThrew的核心判定可以概括为三条规则全部调用都抛出指定异常→ 通过不产生任何错误存在任意一次调用没有抛出指定异常或没有抛出异常→ 失败一次都没有被调用过→ 视为不满足总是抛出的前提断言同样失败。失败时抛出的错误是ErrorAssertError消息格式定义在 src/sinon/assert.jsmirrorPropAsAssertion(alwaysThrew, %n did not always throw exception%C);即默认错误消息为对象名 did not always throw exception其中%n是 spy/fake/stub 的函数名匿名时用anonymous%C是错误发生的调用栈。相比直接调用spy.alwaysThrew()得到true/false断言失败会携带函数名与调用上下文便于快速定位。三、完整示例两种异常匹配方式原文档给出了可直接运行的完整示例这里完整保留并补充失败分支的解读import * as sinon from sinon; // 场景一fake 从未抛出异常 const f1 sinon.fake(); f1(apple pie); sinon.assert.alwaysThrew(f1, TypeError); // Uncaught Error [AssertError]: fake did not always throw exception // 原因f1 唯一的一次调用没有抛异常all calls threw 不成立 // 场景二fake 每次都抛出指定类型的异常 const f2 sinon.fake.throws(new TypeError(not an apple pie)); try { f2(apple pie); } catch (err) { // 业务代码中通常在此处理异常测试中需显式捕获以继续后续断言 } // Generates no error —— 断言通过 sinon.assert.alwaysThrew(f2, TypeError);关键点解读sinon.fake.throws(new TypeError(not an apple pie))创建了一个每次调用都会抛出该异常实例的 fake参见 fakes 文档 中的throws行为由于f2抛出的是TypeError实例TypeError字符串会通过this.exception.name error分支命中调用f2(apple pie)时必须用try/catch包裹否则异常会直接穿透测试若希望按对象引用匹配可传入相同的异常实例sinon.assert.alwaysThrew(f2, new TypeError(...))仅在传入实例与抛出实例严格相等时通过。四、在测试框架中使用基于仓库测试用例仓库在 docs/tests/docs/assertions/api/always-threw.test.js 中提供了使用tap测试框架的完整验证用例覆盖了上述两条核心路径import tap from tap; import * as sinon from sinon; // 用例一所有调用都抛出异常时断言通过 tap.test(assert.alwaysThrew - passes when all calls threw, (t) { const fake sinon.fake.throws(new Error(boom)); try { fake(); } catch (e) {} try { fake(); } catch (e) {} t.doesNotThrow(() { sinon.assert.alwaysThrew(fake); }, assertion should pass when all calls threw); t.end(); }); // 用例二某次调用没有抛出异常时断言失败 tap.test(assert.alwaysThrew - fails when one call didnt throw, (t) { const fake sinon.fake(); fake(); t.throws( () sinon.assert.alwaysThrew(fake), /fake did not always throw exception/, assertion should fail when not all calls threw ); t.end(); });这两条用例可以视为alwaysThrew的行为契约正向用例fake连续两次抛出异常无论是否传exception断言不抛错反向用例fake存在一次未抛出异常的调用断言抛出错误且错误消息匹配/fake did not always throw exception/。在 Jest、Mocha、Vitest 等框架中用法完全一致——sinon.assert的失败形式统一为抛出Error可以自然地被各框架的expect(() ...).toThrow()、assert.throws等机制捕获。五、alwaysThrew与threw的区别容易混淆的一对 API 是assert.threw与assert.alwaysThrew断言判定粒度通过条件失败消息assert.threw(spy, exception)至少一次存在至少一次调用抛出指定异常%n did not throw exceptionassert.alwaysThrew(spy, exception)全部调用每一次调用都抛出指定异常%n did not always throw exception从 src/sinon/proxy.js 的注册代码可以清晰看到两者在代理层的关联delegateToCalls(proxyApi, threw, true); delegateToCalls(proxyApi, alwaysThrew, false, threw);alwaysThrew被注册为delegateToCalls的代理方法matchAny false并把实际判定委托给每个调用的threw方法——也就是说alwaysThrew就是对每一次调用的threw结果取逻辑与。同样地returned/alwaysReturned、calledWithNew/alwaysCalledWithNew也是成对出现的模式完全一致。六、源码级原理delegateToCalls如何实现总是语义alwaysThrew的底层实现位于 src/sinon/proxy-call-util.js 的delegateToCalls函数。其核心逻辑可以概括为未调用保护如果 proxy 从未被调用!this.called且没有传入notCalled兜底函数直接返回false——对应零次调用不满足 always 断言的语义遍历全部调用从第 0 次到第callCount - 1次逐次取出this.getCall(i)调用其actual指定的方法此处为threw并收集结果matchAny开关matchAny true如threw只要遇到一次匹配就提前return truematchAny false如alwaysThrew必须全部调用都返回true最终return matches this.callCount才通过。这段代码揭示了alwaysThrew与threw本质上是同一个delegateToCalls机制下全部匹配与任一匹配两个分支行为可预期、可推导。而断言层的错误消息则由mirrorPropAsAssertion(alwaysThrew, %n did not always throw exception%C)提供src/sinon/assert.js将false结果转换为携带对象名与调用栈的AssertError。七、适用场景与使用建议验证错误路径的完备性当被测函数在多种非法输入下都应抛出同一类异常例如参数校验抛TypeError时用alwaysThrew可以一次性断言所有路径都抛了比逐个断言更简洁与fake.throws搭配做负面契约测试sinon.fake.throws(...)创建的 fake 天然满足总是抛出适合作为异常来源的替身注入被测模块参考 fakes 文档 与 stubs 文档注意异常被吞的场景如果被观测对象内部用try/catch吞掉了异常alwaysThrew会因为没有调用抛出异常而失败——这恰好是有价值的回归信号优先使用sinon.assert而非裸方法直接调用fake.alwaysThrew(...)只返回布尔值失败时缺少上下文而断言版会给出函数名与调用栈配合sinon.assert.expose或自定义sinon.assert.fail可以无缝接入团队既有的断言框架。完整的断言 API 清单与其余兄弟方法threw、called、calledWith等可查阅 Assertions API 文档其失败消息与实现模式均与alwaysThrew保持一致。赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐SingleFile完整指南如何一键保存完整网页到单个HTML文件SingleFile完整指南如何一键保存完整网页到单个HTML文件 在信息时代我们每天都会遇到需要保存的宝贵网页内容——研究资料、技术文档、新闻文章或是值测试开发工具gh_mirrors/as/assert函数断言测试PHP函数是否抛出预期异常gh_mirrors/as/assert函数断言测试PHP函数是否抛出预期异常 在PHP开发中验证函数是否按预期抛出异常是保证代码健壮性的重要环节。本文将详后端Beekeeper Studio 开源 UI Kit 之 Data Editor集成表格、实体树与 SQL 编辑器的数据编辑组件开发指南Beekeeper Studio 开源 UI Kit 之 Data Editor集成表格、实体树与 SQL 编辑器的数据编辑组件开发指南 导读 Data Ed测试开发工具上一篇DotNetJS 使用教程下一篇ItsycalMac菜单栏上的终极轻量级日历工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
