尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

sinon.assert.alwaysCalledWithMatch 详解:验证 fake/spy/stub 每次调用参数全部匹配

发布时间:2026/9/24 14:06:31

资讯中心
01
ARTICLE

sinon.assert.alwaysCalledWithMatch 详解:验证 fake/spy/stub 每次调用参数全部匹配

sinon.assert.alwaysCalledWithMatch 详解:验证 fake/spy/stub 每次调用参数全部匹配
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),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。