ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Sinon `assert.calledOnceWithMatch` 断言完全指南:精确匹配一次调用

Sinon `assert.calledOnceWithMatch` 断言完全指南:精确匹配一次调用 测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载本文基于 Sinon.JS 官方文档docs/concepts/assertions/api/called-once-with-match.md展开系统讲解assert.calledOnceWithMatch的语义、与calledOnceWithExactly的差异、与sinon.match的配合方式并结合仓库源码与测试用例深入其实现原理。读完本文你将能够在单元测试中准确验证“某个 fake/spy/stub 恰好被调用一次且参数与预期匹配”并学会如何解读其 AssertError 报错信息。一、API 速览与核心语义assert.calledOnceWithMatch是 Sinon 内置断言集合中的一个方法签名如下sinon.assert.calledOnceWithMatch(spy, arg1, arg2, ...)当fake、spy或stub恰好被调用一次且调用参数与给定的匹配条件相符时断言通过不产生任何错误否则抛出一个名为AssertError的错误。该断言是calledWithMatch与calledOnce两重约束的组合调用次数约束目标必须恰好被调用 1 次与 assert.calledOnce 一致参数匹配约束目标那次调用的实参必须与期望参数匹配与 assert.calledWithMatch 一致。这里的匹配并非严格相等而是部分匹配 / 子集匹配语义期望参数可以是对象的子集只要实际实参包含期望对象中列出的所有属性即可通过。其失败信息统一为expected %n to be called once and with match %D该消息定义于 src/sinon/assert.js其中%n会被替换为 fake/spy/stub 的名称%D会被替换为调用参数的格式化描述。二、基础用法与官方示例文档 docs/concepts/assertions/api/called-once-with-match.md 给出的核心示例直接复现了部分匹配的精髓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.calledOnceWithMatch(fake, applePieExpectation); fake({ name: apple pie, price: 123 }); sinon.assert.calledOnceWithMatch(fake, applePieExpectation); // Uncaught Error [AssertError]: expected fake to be called once and with match // Call 1: // { name: apple pie, price: 123 } { name: apple pie } // Call 2: // { name: apple pie, price: 123 } { name: apple pie }要点解读第一次调用fake({ name: apple pie, price: 123 })后断言期望对象{ name: apple pie }是实际实参{ name: apple pie, price: 123 }的子集因此部分匹配成功不抛错。第二次调用之后目标被调用了两次不再满足called once约束断言抛出AssertError。错误消息会逐个列出每一次调用的实际参数与期望参数{ name: apple pie, price: 123 } { name: apple pie }帮助你快速定位是哪一次调用、哪一个参数不匹配。三、匹配语义详解为什么是match而不是exact要正确使用calledOnceWithMatch必须区分它与两个相近断言断言方法调用次数要求参数要求assert.calledOnce恰好 1 次不检查参数assert.calledOnceWith恰好 1 次参数浅相等deepEqual语义assert.calledOnceWithExactly恰好 1 次参数完全相等且数量一致assert.calledOnceWithMatch恰好 1 次参数部分匹配支持嵌套匹配器在 src/sinon/proxy.js 中可以看到calledOnceWithMatch是通过delegateToCalls基于calledWithMatch构建的delegateToCalls( proxyApi, calledOnceWithMatch, true, calledWithMatch, false, undefined, 1, );最后一个参数1即要求调用次数恰好为 1。而calledWithMatch的匹配逻辑最终委托给samsam的createMatcher见 src/sinon/assert.js该库实现了 Sinon 的深度部分匹配算法对对象实际值不能是null/undefined且必须包含期望对象声明的所有属性允许有额外属性对嵌套对象支持递归匹配即匹配器可以嵌套使用对数字要求实参期望值宽松相等对字符串要求实参是字符串且包含期望子串对正则要求实参是字符串且能被正则匹配。因此assert.calledOnceWithMatch非常适合我不关心对象的所有字段只关心关键字段的测试场景例如只校验接口响应中的id、name等关键字段而不必写出完整的期望对象。四、与sinon.match的配合嵌套匹配器assert.calledOnceWithMatch支持传入sinon.match匹配器作为期望参数从而实现更精细的断言。sinon.match的完整能力见 docs/concepts/matchers/api/match.md常用形式包括import * as sinon from sinon; const fake sinon.fake(); fake({ name: apple pie, price: 123, tags: [dessert, sweet] }); // 期望对象 嵌套匹配器 sinon.assert.calledOnceWithMatch(fake, { name: apple pie, price: sinon.match.number, // 要求 price 是数字 tags: sinon.match.array // 要求 tags 是数组 }); // 直接使用匹配器作为整个期望参数 sinon.assert.calledOnceWithMatch(fake, sinon.match.object);与assert.calledWithMatch(spy, sinon.match(arg1), ...)等价参见 assert.calledWithMatch 文档说明calledOnceWithMatch在部分匹配的基础上叠加了恰好一次的约束非常适合验证某个副作用只被触发一次且入参符合预期的场景例如支付接口被调用一次且入参中金额字段符合正则埋点上报被调用一次且事件名与附加属性匹配外部 SDK 初始化被调用一次且配置对象包含关键字段。五、在测试框架中使用官方测试示例官方文档通过 VitePress 测试嵌入指令展示了在测试框架中的真实用法。对应文件为 docs/tests/docs/assertions/api/called-once-with-match.test.js使用 Node 生态常用的tap测试运行器import tap from tap; import * as sinon from sinon; tap.test( assert.calledOnceWithMatch - passes when called once with matching arguments, (t) { const fake sinon.fake(); fake({ name: Alice, age: 30 }); t.doesNotThrow(() { sinon.assert.calledOnceWithMatch(fake, { name: Alice }); }, assertion should pass); t.end(); } ); tap.test(assert.calledOnceWithMatch - fails when called twice, (t) { const fake sinon.fake(); fake({ name: Alice }); fake({ name: Alice }); t.throws( () sinon.assert.calledOnceWithMatch(fake, { name: Alice }), /expected fake to be called once/, assertion should fail when called more than once ); t.end(); }); tap.test( assert.calledOnceWithMatch - fails with non-matching arguments, (t) { const fake sinon.fake(); fake({ name: Bob }); t.throws( () sinon.assert.calledOnceWithMatch(fake, { name: Alice }), /expected fake to be called once/, assertion should fail with non-matching arguments ); t.end(); } );该测试用例覆盖了三条核心路径可以作为你在 Jest / Mocha / Vitest 等框架中迁移的参考通过路径调用一次且参数部分匹配 →doesNotThrow次数失败路径调用两次 → 抛出包含expected fake to be called once的错误参数失败路径调用一次但参数不匹配{ name: Bob }vs{ name: Alice }→ 抛出错误。在任何测试框架中使用时若断言失败抛出的AssertError无法被框架识别为断言错误可通过 assert.fail、assert.expose 与 assert.pass 自定义失败/成功钩子使其与框架集成参见 docs/concepts/assertions/index.md 的 Integrations 一节。六、spy 对象上的同构方法spy.calledOnceWithMatch在介绍断言之外值得一提的是calledOnceWithMatch同时也是 spy 实例上的一个直接方法二者语义一致。spy 版本返回布尔值true/false而assert版本在失败时抛出详细错误。在 test/src/spy-test.js 中测试明确验证了 spy 版本的行为边界describe(.calledOnceWithMatch, function () { beforeEach(function () { this.spy createSpy(); }); it(returns true for exact match, function () { this.spy(1, 2, 3); assert.isTrue(this.spy.calledOnceWithMatch(1, 2, 3)); }); it(returns true for partial match, function () { this.spy(1, 2, 3); assert.isTrue(this.spy.calledOnceWithMatch(1, 2)); }); it(returns false for exact parameters but called more then once, function () { this.spy(1, 2, 3); this.spy(1, 2, 3); assert.isFalse(this.spy.calledOnceWithMatch(1, 2, 3)); }); it(return false for one mismatched call, function () { this.spy(1, 2); assert.isFalse(this.spy.calledOnceWithMatch(1, 2, 3)); }); it(return false for one mismatched call with some other, function () { this.spy(1, 2, 3); this.spy(1, 2); assert.isFalse(this.spy.calledOnceWithMatch(1, 2, 3)); }); });由此可以总结出 spy 版本calledOnceWithMatch的四条判定规则只调用一次且实参完全匹配期望 →true只调用一次实参为期望的超集部分匹配→true调用超过一次即使每次参数都完全匹配→false某次调用的实参无法匹配期望 →false。注意第 4 条中的边界情况即使目标被调用两次其中一次完全匹配、另一次不匹配如最后一条测试spy(1, 2, 3)与spy(1, 2)混合结果依然为false因为恰好一次要求所有调用都必须满足匹配条件。对应地assert.calledOnceWithMatch的失败消息格式为expected %n to be called once and with match %D其定义位置在 src/sinon/assert.js与 spy 方法共享相同的判定逻辑区别仅在于失败时抛出AssertError而非返回false。七、断言错误信息解读当断言失败时AssertError会提供丰富的调试信息。以文档示例为基准错误输出格式为Uncaught Error [AssertError]: expected fake to be called once and with match Call 1: { name: apple pie, price: 123 } { name: apple pie } Call 2: { name: apple pie, price: 123 } { name: apple pie }每一行Call N:之后列出的是该次调用的实际实参与期望参数的对比。当有多次调用时逐条列出可以立即看出是哪一次调用不匹配在部分匹配语义下这里显示的不匹配通常是因为次数超过 1 而整体失败实际参数与期望参数在结构上的差异。从源码结构看错误消息中的%D占位符由assert内部的格式化逻辑替换为实际调用参数调用次数信息则由delegateToCalls传入的调用计数第 7 个参数1驱动src/sinon/proxy.js因此无论是 assert 版本还是 spy 版本失败信息都能精确反映次数 参数两个维度的问题。八、实践建议与常见误区明确区分 match 与 exactly如果业务上要求参数一字不差包括属性数量一致请使用 assert.calledOnceWithExactly只有当你需要容忍额外字段、只关心关键字段时才使用calledOnceWithMatch。善用部分匹配减少脆弱断言对包含时间戳、随机 ID、动态金额的对象直接写完整期望对象会导致测试频繁因无关字段变化而失败用calledOnceWithMatchsinon.match可以显著降低测试脆弱性。次数约束是硬性条件即使每次调用的参数都匹配只要调用次数不是恰好一次断言依然失败——这与calledWithMatch不限制次数有本质区别。集成测试框架时配置 fail/exposeAssertError默认是普通Error如需与 Jest/Chai 等框架的断言机制无缝衔接应参考 docs/concepts/assertions/index.md 自定义sinon.assert.fail。参考资料本文主体文档docs/concepts/assertions/api/called-once-with-match.md断言 API 索引docs/concepts/assertions/api/_index.md断言总览与框架集成docs/concepts/assertions/index.md相近断言assert.calledOnce、assert.calledOnceWithExactly、assert.calledWithMatch匹配器说明docs/concepts/matchers/api/match.md实现源码src/sinon/assert.js、src/sinon/proxy.js单元测试test/src/spy-test.js、docs/tests/docs/assertions/api/called-once-with-match.test.js赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐GalGame翻译工具LunaTranslator实战教程3种提取模式跑通第一条译文GalGame翻译工具LunaTranslator实战教程3种提取模式跑通第一条译文 如果你在玩日文GalGame时卡在语言关LunaTranslator是测试开发工具Sinon Matchers API 完全指南用 sinon.match 实现灵活精确的参数匹配与断言Sinon Matchers API 完全指南用 sinon.match 实现灵活精确的参数匹配与断言 导读 Sinon 的 Matchers匹配器是测试测试开发工具Sinon assert.callCount 详解精确断言 fake/spy/stub 的调用次数Sinon assert.callCount 详解精确断言 fake/spy/stub 的调用次数 sinon.assert.callCount spy, n测试开发工具上一篇学术排版效率工具天津大学LaTeX模板的规范兼容解决方案下一篇WzComparerR2重新定义冒险岛WZ文件解析与资源提取技术创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表