☰
Sinon 自定义匹配器(Custom Matchers)实战指南:用 sinon.match 工厂定制你的参数匹配逻辑
2026/9/25 3:05:06 网站建设 项目流程
  • 测试
  • 开发工具

【免费下载链接】sinon

Test spies, stubs and mocks for JavaScript.

项目地址:https://gitcode.com/gh_mirrors/si/sinon
点击查看免费下载

导读:当内置匹配器无法精确表达测试预期时,sinon.match工厂允许你把任意「值 → 布尔值」的判定函数升级为可复用的匹配器(matcher),从而在 spy 断言、stub 行为绑定和断言库中实现高度定制的参数匹配。本文将以 Sinon 官方文档 docs/concepts/matchers/custom-matchers.md 为主线,结合仓库源码与配套测试,讲透自定义匹配器的定义契约、组合方式、错误信息机制与底层调用链,读完即可在自己的测试里写出语义清晰、可复用的自定义匹配逻辑。

一、核心概念:匹配器是什么

在进入自定义匹配器之前,先明确 Sinon 中「匹配器(matcher)」的定位。匹配器可以像真实值一样被传给spy.calledWith、spy.calledOn、spy.returned、spy.withArgs以及对应的sinon.assert断言函数。它允许你对预期值进行「更模糊」或「更精确」的描述——例如「任意字符串」「包含某个属性的对象」「匹配某正则的值」。

而自定义匹配器,就是绕过所有内置规则,把判定逻辑完全交给你自己:只要提供一个接受值、返回布尔值的函数,sinon.match工厂就能把它包装成一个标准匹配器对象。

官方文档对自定义匹配器的定义只有两句话,却概括了全部契约:

Custom matchers are created with thesinon.matchfactory. The test function takes a value as the only argument. It must returntrue, when the value matches the expectation andfalseotherwise.

即:

  1. 使用sinon.match工厂创建;
  2. 传入的测试函数只接收一个参数(待匹配的值);
  3. 该函数必须返回true(匹配)或false(不匹配)。

二、快速上手:最小的自定义匹配器

仓库配套测试 docs/tests/docs/matchers/custom-matchers.test.js 给出了一个最小可用示例:

import t from "tap"; import sinon from "sinon"; // 自定义判定函数:值在布尔语义上为真即可匹配 function test(value) { return Boolean(value); } // 用 sinon.match 工厂包装成匹配器 const trueIsh = sinon.match(test); t.test(`custom matcher`, (t) => { const f = sinon.fake(); f("apple pie"); // 只要参数是 truthy 值,就认为调用匹配 t.ok(f.calledWith(trueIsh)); t.end(); });

要点拆解:

  • test函数是纯判定逻辑:它接收实际调用时传入的参数"apple pie",返回Boolean("apple pie") === true,因此f.calledWith(trueIsh)成立;
  • 匹配器可以像普通值一样参与断言:calledWith在比较参数时,一旦发现预期值是匹配器对象,就不再走深比较(deepEqual),而是调用匹配器的测试函数;
  • 工厂函数名字面义明确:sinon.match(test)生成的匹配器语义就是「匹配任何 truthy 值」,完全由你定义的函数说了算。

三、sinon.match 的重载:函数参数与内置匹配器

sinon.match是 Sinon 匹配体系的总入口,它根据参数类型走不同分支。根据官方 API 文档 docs/concepts/matchers/api/match.md,函数形式的参数正是自定义匹配器的入口,其余重载则是内置匹配器:

调用形式匹配规则
sinon.match(number)要求实际值==等于给定数字
sinon.match(string)要求实际值是字符串,且包含该字符串作为子串
sinon.match(regexp)要求实际值是字符串,且匹配给定正则
sinon.match(object)要求实际值非null/undefined,且至少拥有预期对象的所有属性(支持嵌套匹配器)
sinon.match(function)自定义匹配器,规则由你提供的判定函数决定(即本文主题,见 docs/concepts/matchers/custom-matchers.md)

从源码结构看,匹配器工厂并非在 Sinon 仓库内自行实现:在 src/create-sinon-api.js 中可以看到:

match: samsam.createMatcher,

即sinon.match直接指向@sinonjs/samsam包的createMatcher。这意味着自定义匹配器与deepEqual等深度比较逻辑共享同一套基础库,保证匹配器在 spy、assert、mock 各处行为一致。

四、底层原理:匹配器在断言调用链中如何被使用

理解自定义匹配器,最好同时看清它在 Sinon 内部的位置。仓库源码中有三处典型消费场景:

4.1 断言模块:sinon.assert.match

在 src/sinon/assert.js 中,assert.match(actual, expectation)正是用createMatcher把预期值包装后做测试:

match: function match(actual, expectation) { const matcher = createMatcher(expectation); if (matcher.test(actual)) { assert.pass("match"); } else { const formatted = [ "expected value to match", ` expected = ${inspect(expectation)}`, ` actual = ${inspect(actual)}`, ]; failAssertion(this, join(formatted, "\n")); } },

这里揭示了一个关键事实:无论预期值是数字、字符串、对象还是你自定义的函数,Sinon 最终都统一经createMatcher归一化为一个带有.test(value)方法的对象,然后调用matcher.test(actual)得到布尔结果。自定义匹配器只是让test方法的实现变成了你的判定函数。

4.2 调用记录:proxy-call 中的匹配器识别

在 src/sinon/proxy-call.js 中,createMatcher被用于判断调用参数是否与预期匹配,支撑calledWith、withArgs等 API 的底层实现。spy 的withArgs(见 src/sinon/spy.js)会把匹配参数保存在matchingArguments中,后续调用记录比对时一旦发现匹配器就执行其test逻辑,而不是做普通相等比较。

4.3 错误信息:匹配器自带 message

自定义匹配器还有一个经常被忽略的能力——为失败断言提供可读的错误信息。在 src/sinon/spy-formatters.js 中:

function colorSinonMatchText(matcher, calledArg, calledArgMessage) { let calledArgumentMessage = calledArgMessage; let matcherMessage = matcher.message; if (!matcher.test(calledArg)) { matcherMessage = colorizer.red(matcher.message); // ... } return `${calledArgumentMessage} ${matcherMessage}`; }

格式化器会读取matcher.message并配合matcher.test(calledArg)的结果做颜色标记:匹配失败时把匹配器说明标红、实际参数标绿,从而在断言失败输出中清晰展示「预期是什么、实际传了什么」。

关于message的现状:官方文档 docs/concepts/matchers/custom-matchers.md 中以 TODO 注释的形式记录了一个待确认问题——第二参数(message)目前主要在sinon.assert体系用于生成错误信息,仓库维护者尚在评估是否继续保留该参数。从spy-formatters.js的读取逻辑可以推断,message字段已经实际参与了失败信息的格式化,因此为自定义匹配器设置清晰的描述文本(或依赖默认 message)有助于提升断言失败时的可读性。

五、组合使用:and / or / not 让匹配器表达力倍增

单个自定义匹配器解决单一判定,而 Sinon 为所有匹配器内置了逻辑组合能力。官方文档 docs/concepts/matchers/combining-matchers.md 说明:

All matchers implementandandor. This allows to logically combine multiple matchers. The result is a new matcher that requires both (and) or one of the matchers (or) to returntrue.

配套测试 docs/tests/docs/matchers/combining-matchers.test.js 演示了两种典型组合:

// 或组合:字符串或数字都算匹配 const stringOrNumber = sinon.match.string.or(sinon.match.number); const f = sinon.fake(); f("apple pie"); t.ok(f.calledWith(stringOrNumber)); // 与组合:必须是 Book 实例,且拥有 pages 属性 const bookWithPages = sinon.match .instanceOf(Book) .and(sinon.match.has("pages")); const b = new Book(42); const h = sinon.fake(); h(b); t.ok(h.calledWith(bookWithPages));

把自定义匹配器与内置匹配器组合,即可构造「既符合我自定义规则、又属于某类型」「满足自定义规则或落入内置规则」这类复合预期。组合后返回的依然是一个标准匹配器对象,因此可以继续参与calledWith、withArgs、assert等所有场景,也可以继续链式组合。

六、实战模式:自定义匹配器的典型使用场景

综合文档与源码,自定义匹配器最适合以下几类场景:

  1. 语义化断言:把复杂的判定条件命名成一个有业务含义的匹配器(如上面的trueIsh),让测试读起来像自然语言;
  2. 跨用例复用:把判定函数抽到公共模块,在多个测试文件间共享同一份匹配逻辑,避免断言条件散落重复;
  3. 与withArgs配合绑定 stub 行为:withArgs接受匹配器,因此可以用自定义匹配器精确圈定「哪些参数组合」时 stub 该返回什么(见 spy.withArgs 与 stub.withArgs);
  4. 与assert.match配合做对象结构校验:自定义匹配器可以作为assert.match(actual, expectation)的 expectation,实现深度结构之外的业务规则校验。

七、注意事项与边界

  • 返回值必须是严格布尔:判定函数必须返回true或false。若返回其他 truthy 值,虽然测试可能「碰巧」通过,但会破坏匹配器契约,应使用Boolean()显式转换(如官方测试所示);
  • 只接收一个参数:判定函数签名是(value),其余参数会被忽略,不要把期望值也塞进函数参数里——它应当是闭包捕获或写死在函数体内的;
  • 失败信息依赖 message:想让失败输出更友好,请关注匹配器对象上 message 字段的生成逻辑(见上文 4.3 节),确保断言失败时能看出匹配器在描述什么;
  • 组合结果仍是匹配器:and/or返回新匹配器,可用于继续链式组合,但注意逻辑要自洽,避免构造出永假的匹配器。

八、小结

自定义匹配器是 Sinon 匹配体系中最灵活的一块拼图:sinon.match(fn)一行代码即可把你的判定函数接入 spy、assert、stub 的整个参数匹配链路。它由@sinonjs/samsam的createMatcher归一化处理(见 src/create-sinon-api.js),经matcher.test(value)驱动判定,通过matcher.message参与失败信息格式化(见 src/sinon/spy-formatters.js),并能与内置匹配器自由组合。官方配套测试 docs/tests/docs/matchers/custom-matchers.test.js 和 docs/tests/docs/matchers/combining-matchers.test.js 是理解全部契约的最佳起点,内置匹配器清单可进一步查阅 docs/concepts/matchers/api/index.md。

  • 测试
  • 开发工具

【免费下载链接】sinon

Test spies, stubs and mocks for JavaScript.

项目地址:https://gitcode.com/gh_mirrors/si/sinon
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询