深入解析 Sinon 的 `assert.alwaysThrew`:验证 spy 是否每次都抛出异常
测试开发工具【免费下载链接】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),仅供参考

相关新闻

《Python程序设计基础》Git 操作指南

《Python程序设计基础》Git 操作指南

1. 引言 在《Python程序设计基础》课程中,除了编写 Python 代码,掌握版本控制工具 Git 同样重要。Git 能帮助你管理代码的历史版本、多人协作开发,以及安全地保存每一次修改。本教程将带你从零开始,掌握 Git 的常用操作&#xff…

2026/9/24 13:44:18 阅读更多 →
背调合规在劳动用工决策边界上的证据要求

背调合规在劳动用工决策边界上的证据要求

背调信息能否进入劳动用工决策,不能只看信息是否被查到,也不能只凭候选人已经授权。较稳妥的边界是:先确认处理目的和合法性基础,再检验信息与岗位的直接关联、收集范围是否最小必要、结果是否经过准确性复核,最后由有…

2026/9/24 13:44:18 阅读更多 →
Cangjie/cangjie_js_interop 类型映射全解析:JSValue 与仓颉类型互转速查手册

Cangjie/cangjie_js_interop 类型映射全解析:JSValue 与仓颉类型互转速查手册

Cangjie/cangjie_js_interop 类型映射全解析:JSValue 与仓颉类型互转速查手册 【免费下载链接】cangjie_js_interop 项目地址: https://gitcode.com/Cangjie/cangjie_js_interop Cangjie/cangjie_js_interop(仓颉与 JavaScript 互操作库&#xf…

2026/9/24 13:44:17 阅读更多 →

最新新闻

告别手动更新!Jackett智能调度系统让种子索引器永远保持最新

告别手动更新!Jackett智能调度系统让种子索引器永远保持最新

告别手动更新!Jackett智能调度系统让种子索引器永远保持最新 你是否还在为种子索引器(Indexer)失效而烦恼?频繁手动更新软件版本、修复配置错误、同步Tracker列表——这些重复操作不仅浪费时间,还会导致下载中断。本文…

2026/9/24 14:22:48 阅读更多 →
企业智能体迈向生产级:跳出Agent工具思维,构建可规模化落地的AI平台底座

企业智能体迈向生产级:跳出Agent工具思维,构建可规模化落地的AI平台底座

随着 AI 智能体技术快速普及,不少企业已经完成 Demo 概念验证,开始尝试将智能体投入真实生产业务。IDC 在《新型企业级 AI 平台正在兴起》报告中提出重要判断:新型企业级 AI 平台并不是大模型与智能体工具的简单叠加。想要让智能体从演示玩具…

2026/9/24 14:22:48 阅读更多 →
OOTDiffusion body_pose_model.pth 找不到快速修复

OOTDiffusion body_pose_model.pth 找不到快速修复

OOTDiffusion body_pose_model.pth 找不到快速修复 【免费下载链接】OOTDiffusion [AAAI 2025] Official implementation of "OOTDiffusion: Outfitting Fusion based Latent Diffusion for Controllable Virtual Try-on" 项目地址: https://gitcode.com/GitHub_Tr…

2026/9/24 14:22:48 阅读更多 →
突破Tracker限制:Jackett多平台推送通知无缝集成方案

突破Tracker限制:Jackett多平台推送通知无缝集成方案

突破Tracker限制:Jackett多平台推送通知无缝集成方案 Jackett是一款强大的API支持工具,为用户喜爱的种子追踪器提供统一的接口服务。通过Jackett,用户可以轻松整合多个Torrent Tracker,实现一站式搜索和管理,同时享受…

2026/9/24 14:22:48 阅读更多 →
Jackett数据导出终极指南:轻松生成CSV/JSON格式的种子数据

Jackett数据导出终极指南:轻松生成CSV/JSON格式的种子数据

Jackett数据导出终极指南:轻松生成CSV/JSON格式的种子数据 Jackett作为最强大的种子索引器代理工具,为全球用户提供了无缝连接600多个公共和私有追踪器的能力。本文将为您详细介绍如何利用Jackett的强大功能,将搜索结果导出为CSV和JSON格式&…

2026/9/24 14:22:47 阅读更多 →
ESPnet 实战指南:多 GPU 训练、run.sh 阶段控制与 CTC/Attention 解码模式切换

ESPnet 实战指南:多 GPU 训练、run.sh 阶段控制与 CTC/Attention 解码模式切换

人工智能语音音频深度学习NLP 【免费下载链接】espnet End-to-End Speech Processing Toolkit 项目地址: https://gitcode.com/gh_mirrors/es/espnet 点击查看 免费下载 本篇技术指南以 ESPnet 官方文档 doc/tutorial.md 为骨架,系统讲解 ESPnet 日常使…

2026/9/24 14:21:47 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →