sinon.assert.alwaysCalledWithMatch 详解验证 fake/spy/stub 每次调用参数全部匹配【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址: https://gitcode.com/gh_mirrors/si/sinonsinon.assert.alwaysCalledWithMatch(spy, arg1, arg2, ...)是 Sinon.JS 内置断言之一用于验证fake、spy或stub的每一次调用参数都能与给定的期望值支持部分匹配与sinon.match匹配器相匹配。本文以 docs/concepts/assertions/api/always-called-with-match.md 为主线结合源码、测试与相关断言 API帮助你掌握该断言的语义、用法、底层实现以及它与alwaysCalledWith等兄弟断言的区别并在测试框架中正确落地使用。一、断言签名与语义1. 函数签名sinon.assert.alwaysCalledWithMatch(spy, arg1, arg2, ...)spy被验证的fake、spy或stub对象arg1, arg2, ...期望参数列表。每个参数既可以是普通值做深度部分匹配也可以是sinon.match匹配器做条件匹配。2. 核心语义该断言通过不抛错当且仅当目标 fake/spy/stub 的每一次调用都满足以下两个条件该次调用的实际参数数量不少于期望参数数量允许实际调用多传参数多出的部分被忽略期望参数列表中的每一个参数都能与对应的实际参数匹配。只要有一次调用的参数不满足匹配要求断言即失败并抛出AssertError。从文档的等价描述可以更精确地理解它的行为This behaves the same way assinon.assert.alwaysCalledWith(spy, sinon.match(arg1), sinon.match(arg2), ...)也就是说alwaysCalledWithMatch本质上是把每个期望参数先包装成sinon.match(...)匹配器再调用alwaysCalledWith逐次验证。这也是它与alwaysCalledWith严格深度相等的核心差异所在。3. 与calledWithMatch的区别alwaysCalledWithMatch与calledWithMatch的差别仅在是否要求所有调用都匹配calledWithMatch只要存在一次调用匹配即通过matchAnyalwaysCalledWithMatch所有调用都必须匹配。类似的配对关系也存在于calledWith/alwaysCalledWith、calledWithExactly/alwaysCalledWithExactly等断言中详见 Assertions API 索引。二、文档示例用 object 期望做部分匹配官方文档给出了一个非常典型的实战场景用一个部分对象作为期望值验证每次调用传入的对象都包含指定字段。import * as sinon from sinon; const fake sinon.fake(); const applePieExpectation { name: apple pie }; fake({ name: apple pie, price: 123 }); // Matches, generates no error sinon.assert.alwaysCalledWithMatch(fake, applePieExpectation); fake({ name: cherry pie, price: 123 }); sinon.assert.alwaysCalledWithMatch(fake, applePieExpectation); // Uncaught Error [AssertError]: expected fake to always be called with match // Call 1: // { name: apple pie, price: 123 } { name: apple pie } // Call 2: // { name: cherry pie, price: 123 } { name: apple pie }解读第一次调用传入{ name: apple pie, price: 123 }期望对象{ name: apple pie }是其子集因此匹配成功第二次调用传入{ name: cherry pie, price: 123 }name字段值不同匹配失败由于所有调用都必须匹配断言整体失败抛出AssertError错误信息中逐行列出每次调用的实际参数与期望参数便于快速定位是第几次调用出的问题。这个错误信息格式由 src/sinon/assert.js 中注册的断言消息模板决定mirrorPropAsAssertion( alwaysCalledWithMatch, expected %n to always be called with match %D, );其中%n会被替换为 fake 的名称%D会展开为参数详情列表。三、结合sinon.match匹配器使用alwaysCalledWithMatch最有价值的用法是与sinon.match提供的类型/条件匹配器组合对只关心关键字段、不关心其余细节的场景做精确断言。官方配套测试 docs/tests/docs/assertions/api/always-called-with-match.test.js 展示了这一用法import tap from tap; import * as sinon from sinon; tap.test(assert.alwaysCalledWithMatch - passes when all calls match, (t) { const fake sinon.fake(); fake({ name: Alice, age: 30 }); fake({ name: Bob, age: 40 }); t.doesNotThrow(() { sinon.assert.alwaysCalledWithMatch(fake, { age: sinon.match.number }); }, assertion should pass when all calls match); t.end(); }); tap.test( assert.alwaysCalledWithMatch - fails when one call doesnt match, (t) { const fake sinon.fake(); fake({ name: Alice }); fake({ name: Bob, age: 40 }); t.throws( () sinon.assert.alwaysCalledWithMatch(fake, { age: sinon.match.number }), /expected fake to always be called with match/, assertion should fail when not all calls match ); t.end(); } );这个测试用例揭示了两个实用点部分对象 匹配器嵌套期望值{ age: sinon.match.number }表示实际参数必须是对象且age字段是数字。只要每次调用都满足该条件断言就通过完全不用关心name等其它字段。失败判据一旦某次调用缺少age字段如{ name: Alice }断言失败错误信息匹配/expected fake to always be called with match/。sinon.match内置了大量匹配器例如sinon.match.number、sinon.match.string、sinon.match.object、sinon.match.any、sinon.match.has(key, value)等完整列表见 Matchers API。这些匹配器都可直接作为alwaysCalledWithMatch的期望参数使用。四、底层实现原理1. 断言入口mirrorPropAsAssertion模板sinon.assert.alwaysCalledWithMatch并非手写逻辑而是通过mirrorPropAsAssertion工厂函数从 fake 的alwaysCalledWithMatch属性自动生成的。见 src/sinon/assert.jsfunction mirrorPropAsAssertion(name, method, message) { assert[name] function (fake) { verifyIsStub(fake); const args arraySlice(arguments, 1); let failed false; ... failed typeof fake[meth] function ? !fake[meth].apply(fake, args) : !fake[meth]; if (failed) { failAssertion( this, (fake.printf || fake.proxy.printf).apply( fake, concat([msg], args), ), ); } else { assert.pass(name); } }; }调用链为verifyIsStub(fake)先校验传入对象确实是一个 fake/spy/stub否则直接assert.fail调用fake.alwaysCalledWithMatch(...)若返回false则通过failAssertion抛出AssertError否则走assert.pass。2. proxy 层delegateToCalls委派在 src/sinon/proxy.js 中alwaysCalledWithMatch通过delegateToCalls委派到每个调用的calledWithMatchdelegateToCalls(proxyApi, calledWithMatch, true); delegateToCalls(proxyApi, alwaysCalledWith, false, calledWith); delegateToCalls(proxyApi, alwaysCalledWithMatch, false, calledWithMatch);其中第二个参数matchAny是关键calledWithMatch传true任一调用匹配即通过alwaysCalledWithMatch传false必须全部调用匹配。delegateToCalls的实现见 src/sinon/proxy-call-util.jsproxy[method] function () { if (!this.called) { ... 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; } } } ... return matches this.callCount; };可以看到对于alwaysCalledWithMatchmatchAny false实现会遍历 fake 的全部历史调用逐次用calledWithMatch验证只有当匹配次数等于总调用次数时才返回true。从源码结构看这一逐调用聚合逻辑正是 always 语义的来源。3. 单次调用匹配proxy-call.calledWithMatch真正执行单次调用是否匹配的是 src/sinon/proxy-call.js 中的calledWithMatchcalledWithMatch: function calledWithMatch() { const self this; const calledWithMatchArgs slice(arguments); if (calledWithMatchArgs.length self.args.length) { return false; } return reduce( calledWithMatchArgs, function (prev, expectation, i) { const actual self.args[i]; return prev match(expectation).test(actual); }, true, ); },关键点参数数量下限期望参数数量不能超过实际参数数量否则直接返回false。也就是说alwaysCalledWithMatch允许实际调用多传参数这与alwaysCalledWithExactly要求严格等长的语义不同逐位匹配对每个期望参数调用match(expectation).test(actual)——即sinon.match匹配器对实际参数进行测试。普通值会被包装成深度部分匹配器sinon.match对象则直接使用其test逻辑。match()函数来自sinonjs/samsam的createMatcher见 src/sinon/assert.js其部分对象匹配能力正是文档示例中{ name: apple pie }能够匹配{ name: apple pie, price: 123 }的根本原因。五、在测试框架中的实战用法1. 原生 Node.js 断言 / tap如前文测试所示直接使用即可const fake sinon.fake(); fake({ status: 200, body: ok }); fake({ status: 200, body: ok }); sinon.assert.alwaysCalledWithMatch(fake, { status: 200 }); // 通过2. 与assert.expose配合简化写法如果不想每次写sinon.assert.前缀可以用assert.expose将断言方法挂载到全局或某个对象上sinon.assert.expose(globalThis, { prefix: }); alwaysCalledWithMatch(fake, { status: 200 });expose的完整参数prefix、includeFail见 expose 文档 与 src/sinon/assert.js 的实现。3. 与 jest、mocha 等框架集成Sinon 官方的断言体系天然适用于各类测试框架。当断言失败时抛出的是Error且error.name AssertError见 src/sinon/assert.js因此可以在 Mocha/Jest 中直接用expect(() sinon.assert.alwaysCalledWithMatch(...)).toThrow()捕获失败配合 sinon-chai 等集成库使用更符合 Chai 风格的断言链。关于断言与外部框架的集成策略自定义assert.fail、assert.pass参见 Assertions 概念页。六、常见误区与最佳实践不要与alwaysCalledWithExactly混淆alwaysCalledWithMatch允许实际调用多传参数只校验前缀位置的期望参数而alwaysCalledWithExactly要求参数个数与值都严格相等always 意味着所有调用即使 fake 只被调用过一次且匹配只要后续有一次不匹配断言整体失败。若只想验证至少某次调用匹配应改用calledWithMatch优先使用部分对象期望验证对象参数时只写关键字段即可避免过度耦合不相关的字段让测试更聚焦于行为契约错误信息是调试利器失败时AssertError会列出每次调用的实际/期望参数配合sinon.assert.expose集成到测试报告可快速定位是哪一次调用偏离了预期。七、相关 API 速查断言方法语义对应文档calledWithMatch存在一次调用参数匹配即通过called-with-matchalwaysCalledWithMatch所有调用参数都匹配才通过本文alwaysCalledWith所有调用与期望参数深度相等always-called-withalwaysCalledWithExactly所有调用参数个数与值严格相等always-called-with-exactlyneverCalledWithMatch没有任何调用的参数匹配never-called-with-match八、源码与测试参考断言注册与错误消息模板src/sinon/assert.js断言通用实现mirrorPropAsAssertionsrc/sinon/assert.jsproxy 层委派alwaysCalledWithMatchsrc/sinon/proxy.js逐调用聚合逻辑delegateToCallssrc/sinon/proxy-call-util.js单次调用匹配calledWithMatchsrc/sinon/proxy-call.js配套测试含sinon.match用法docs/tests/docs/assertions/api/always-called-with-match.test.js九、小结sinon.assert.alwaysCalledWithMatch是验证多次调用的参数始终满足某类约束的首选断言它结合了 Sinon 的两大能力——assert系列断言提供的详细失败信息以及sinon.match匹配器提供的灵活部分匹配。理解其逐调用聚合 单调用匹配的双层实现delegateToCalls→proxy-call.calledWithMatch能帮助你判断它与其他alwaysCalledWith*断言的边界写出更稳健、可维护的测试代码。【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址: https://gitcode.com/gh_mirrors/si/sinon创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
中性原子核心瓶颈:原子丢失与比特存活|研究进展、判定标准、实验硬件条件 一颗原子跑掉,一段量子计算就可能前功尽弃。中性原子量子计算这两年很热。室温运行、可大规模扩展、成本相对可控,让它在超导、离子阱、光量子之外,迅速成为资本和科研界争相押注的路线。但很多人只看到它的光鲜,却忽略了一个横在… · 2026/9/24 14:06:30
AI 时代,PostgreSQL 正在发生什么变化? 引言
过去两年,AI 几乎重塑了整个技术行业的话题中心。
RAG、Agent、向量数据库、GraphRAG、AI Coding……新概念层出不穷。与此同时,数据库领域也在经历一场静水深流的变革。
2025 年,PostgreSQL 在全球开发者中的使用率达到了创纪录的 5… · 2026/9/24 14:06:24
2026 人才测评工具优选指南:核心选型标准全梳理 一、选型前先想清楚一件事:测评工具解决什么问题引文/摘要企业在人才决策中面临的核心矛盾,往往是主观判断与客观数据之间的断层。2026年,人才测评工具已从单纯的“筛人”演变为组织人才数据中台的基础组件。然而工具品类繁多,能力… · 2026/9/24 14:42:40
cloudflare-os 仓库 PR 评审指南:AI 评审者的内核安全、密钥防护与构建系统检查清单 人工智能AI 应用AI AgentAgent 沙箱AI 安全治理 【免费下载链接】cloudflare-os Agent workspace built on Cloudflare Workers for creating documents, building apps, and running agents with your company’s context and systems. 项目地址: https://gitcode.… · 2026/9/24 14:42:40
WinUtil 完全指南:免费 Windows 系统优化工具,4 类维护一次完成 WinUtil 完全指南:免费 Windows 系统优化工具,4 类维护一次完成 【免费下载链接】winutil Chris Titus Techs Windows Utility - Install Programs, Tweaks, Fixes, and Updates 项目地址: https://gitcode.com/GitHub_Trending/wi/winutil
维护 … · 2026/9/24 14:42:40
Claude Code嵌入式AI编程实战:SPI/I2C/ADC驱动开发指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 14:42:40
python代码保存到Gitee完整步骤 一、环境准备
1.本地安装Git
Git - Install for Windows 2.创建Gitee账号
3.创建本地Python-study文件夹 二、完整步骤操作
1.在python-study文件夹里右键选择Open Git Bash here开Git终端 2.输入Gitee中的代码 3.弹出登录界面 登录成功后 点击刷新 弹出以下界面 4.点击“第… · 2026/9/24 14:42:40
卡瓦牙椅E50说明书第三部分:挂架系统、参数设置与维护保养全解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 14:42:33
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44