- 测试
- 开发工具
【免费下载链接】sinon
Test spies, stubs and mocks for JavaScript.
导读
sinon.match.symbol是 Sinon 匹配器(Matcher)体系中用于强制校验参数必须是Symbol类型的专用匹配器。在编写测试时,当被测函数接收 Symbol 作为参数(例如键、枚举标识或迭代器协议符号),而你又想确保断言不会被字符串、对象等近似值"蒙混过关"时,sinon.match.symbol就是最直接的表达方式。本文将以官方文档 symbol.md 为主体,结合仓库中的测试用例与源码实现,讲透它的匹配语义、验证方式、使用场景与底层调用链,让你能安全地将它用于 spy、stub 与 assert 的各种断言场景。
匹配器(Matcher)机制速览
在深入sinon.match.symbol之前,先明确它所属的体系。Sinon 官方文档 Matchers 总览 明确指出:
Matchers can be passed as arguments to
spy.calledOn,spy.calledWith,spy.returnedand the correspondingsinon.assertfunctions as well asspy.withArgs. Matchers allow to be either more fuzzy or more specific about the expected value.
即匹配器是一类"期望值描述符",可被当作参数传给以下断言入口:
spy.calledOn(文档):匹配调用时的this值;spy.calledWith(文档):匹配调用参数;spy.returned(文档):匹配返回值;- 对应的
sinon.assert系列函数(文档),如assert.calledWithMatch; spy.withArgs(文档):按参数筛选 spy 的调用记录。
匹配器允许你表达"模糊匹配"(例如任意字符串、任意数字)或"精确类型匹配"(例如必须是 Symbol)。sinon.match.symbol属于后者:它把匹配标准收窄到 ECMAScript 原生的Symbol类型上。
sinon.match.symbol的官方语义
sinon.match.symbol的官方定义极其简洁,就一句话(symbol.md):
Requires the value to be a
Symbol.
翻译过来就是:要求被匹配的值必须是一个Symbol。这意味着:
- 传入
Symbol("test")这类用户创建的 Symbol →通过; - 传入
Symbol.iterator、Symbol.toStringTag这类内建的 well-known Symbol →通过; - 传入字符串
"Symbol(test)"(即使内容长得像 Symbol)→不通过; - 传入任何对象(例如
{ type: "symbol" })→不通过。
它的判定依据是值的运行时类型,而不是值的"外形"或字符串描述,这一点与sinon.match.string、sinon.match.number等同类类型匹配器保持一致的语义。
测试用例:官方行为验证
仓库中与该文档对应的官方测试位于 symbol.test.js,它使用 tap 测试框架完整覆盖了上述四种情形,是对sinon.match.symbol行为最权威的验证:
import tap from "tap"; import * as sinon from "sinon"; tap.test("sinon.match.symbol", (t) => { const fake = sinon.fake(); fake(Symbol("test")); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.symbol); }, "should accept Symbol"); fake(Symbol.iterator); t.doesNotThrow(() => { sinon.assert.calledWithMatch(fake, sinon.match.symbol); }, "should accept well-known Symbol"); fake.resetHistory(); fake("Symbol(test)"); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.symbol), /expected fake to be called with match/, "should reject string" ); fake.resetHistory(); fake({ type: "symbol" }); t.throws( () => sinon.assert.calledWithMatch(fake, sinon.match.symbol), /expected fake to be called with match/, "should reject object" ); t.end(); });逐段解读这个测试:
- 接受普通 Symbol:
fake(Symbol("test"))之后,assert.calledWithMatch(fake, sinon.match.symbol)不会抛出异常,说明用户创建的带描述 Symbol 可以正常通过匹配; - 接受 well-known Symbol:
fake(Symbol.iterator)同样通过断言。这确认了匹配器对内置协议符号(如Symbol.iterator、Symbol.asyncIterator、Symbol.hasInstance等)同样生效,并不局限于用户自定义 Symbol; - 拒绝字符串:
fake("Symbol(test)")后调用同样的断言会抛出异常,异常消息匹配/expected fake to be called with match/。这表明"长得像 Symbol 的字符串"不会被误判为 Symbol; - 拒绝对象:
fake({ type: "symbol" })同样触发断言失败,说明携带type: "symbol"属性的普通对象也无法通过匹配。
测试中两次fake.resetHistory()用于清空调用历史,保证每个断言只针对当前这一次调用,这也是使用 Sinon spy/fake 时的良好实践。
底层原理:匹配器如何被消费
sinon.match.symbol之所以能无缝用于calledWith、calledOn、assert.calledWithMatch、mock 期望校验等多种入口,是因为 Sinon 在多个消费点上统一通过match.isMatcher(...)识别"传入的是一个匹配器",然后调用其test(value)方法完成判定。从源码可以梳理出三条关键调用链:
1. spy 调用记录的 this 匹配(proxy-call.js)
const callProto = { calledOn: function calledOn(thisValue) { if (match.isMatcher(thisValue)) { return thisValue.test(this.thisValue); } return this.thisValue === thisValue; }, // ... };当calledOn收到的参数是一个匹配器时,走thisValue.test(this.thisValue)路径——即用匹配器去测试真实调用时的this值。这也解释了为什么匹配器(包括sinon.match.symbol)能用于calledOn场景。
2. mock 期望的参数校验(mock-expectation.js)
function verifyMatcher(possibleMatcher, arg) { const isMatcher = match.isMatcher(possibleMatcher); return (isMatcher && possibleMatcher.test(arg)) || true; }verifyMatcher同样遵循"是匹配器就调用test"的约定,使sinon.match.symbol可以直接写进 mock 的expects(...).withExactArgs(...)等期望里。
3. 断言失败信息的格式化(spy-formatters.js)
if (match.isMatcher(expectedArg)) { message += colorSinonMatchText(expectedArg, calledArg); }断言失败时,Sinon 会对匹配器进行特殊着色与格式化输出,让expected fake to be called with match ...这类错误信息更易读。在 assert.js 中,calledWithMatch断言对应的失败模板为"expected %n to be called with match %D",与测试中断言t.throws(..., /expected fake to be called with match/)观察到的消息格式完全吻合。
Symbol 在 Sinon 内部的另一处使用:非枚举类型标记
值得一提的是,Symbol 不仅是sinon.match.symbol匹配的目标类型,Sinon 自身在实现上也依赖 Symbol 的"唯一性 + 非枚举"特性。在 sinon-type.js 中:
const sinonTypeSymbolProperty = Symbol("SinonType"); // ... set(object, type) { Object.defineProperty(object, sinonTypeSymbolProperty, { value: type, configurable: false, enumerable: false, }); }, get(object) { return object && object[sinonTypeSymbolProperty]; },Sinon 用Symbol("SinonType")作为内部属性键,通过Object.defineProperty将其设置为不可枚举(enumerable: false),从而在对象上标注类型信息(spy、stub、fake 等),同时避免该标记出现在for...in、Object.keys()或 JSON 序列化中。这从侧面说明了 Symbol 作为"隐形标识符"的工程价值——当你需要为系统内对象附加元数据、又要保证外部接口不被污染时,Symbol 键是标准做法。而sinon.match.symbol恰好就是为验证这类使用模式而存在的类型匹配器。
实战应用场景
结合上述行为与源码证据,sinon.match.symbol的典型应用场景包括:
1. 校验以 Symbol 作为键或标识的函数调用
const token = Symbol("request-id"); const handler = sinon.fake(); const client = { send: (id) => handler(id) }; client.send(token); // 校验确实收到的是 Symbol 标识,而不是字符串 sinon.assert.calledWithMatch(handler, sinon.match.symbol);当 API 约定使用 Symbol 作为内部标识(避免与字符串命名空间冲突)时,用sinon.match.symbol能在不关心具体是哪个 Symbol 的情况下,只校验"类型正确"。
2. 与spy.withArgs组合做调用分拣
const spy = sinon.spy(); spy(Symbol.iterator); spy("plain string"); const symbolCalls = spy.withArgs(sinon.match.symbol); symbolCalls.calledOnce; // true,只有第一次调用被筛出参考 withArgs 文档,withArgs允许按匹配器筛选调用记录,与sinon.match.symbol组合即可快速统计"传入了 Symbol 参数的调用次数"。
3. 校验迭代器协议相关实现
const obj = { [Symbol.iterator]: function* () { yield 1; } }; const stub = sinon.stub(obj, Symbol.iterator); // ... 触发迭代 stub.calledWithMatch(sinon.match.symbol); // 校验入参是 Symbol 类型如前所述,well-known Symbol(如Symbol.iterator)同样能被sinon.match.symbol接受,因此可用于校验实现了迭代器协议的对象——注意sinon.stub也支持以 Symbol 作为属性名进行打桩。
注意事项与边界
- 它是类型匹配,不是值匹配:
sinon.match.symbol只检查typeof value === "symbol",不关心具体是哪个 Symbol。若要匹配某个特定的 Symbol 值,应直接传入该 Symbol 本身(例如fake.calledWith(specificSymbol)),匹配器无法表达"等于某个具体 Symbol"。 - 字符串描述不影响判定:
Symbol("x")和Symbol("y")是不同值,但都会被sinon.match.symbol接受;而"x"、new String("x")则一律拒绝。这与测试用例中"拒绝字符串'Symbol(test)'"的行为一致。 - 兼容性前提:
Symbol是 ES2015(ES6)特性,使用本匹配器需要运行环境支持原生 Symbol。在较老的 Node.js 或浏览器环境中,typeof Symbol可能不是"function",此时应像 assert-test.js 中测试"symbol method names"时做的那样,先判断typeof Symbol !== "function"并跳过相关用例。 - 适用于所有支持匹配器的断言入口:
calledWith、calledOn、returned、assert.calledWithMatch、withArgs以及 mock 期望均可使用,消费逻辑统一由match.isMatcher+test(value)协议承担(见上文源码调用链)。
小结
sinon.match.symbol是 Sinon 匹配器家族中语义最清晰、用途最精准的一员:它把断言收窄到"必须是 Symbol 类型",既接受用户自定义 Symbol,也接受Symbol.iterator等 well-known Symbol,同时严格拒绝字符串和普通对象。官方测试 symbol.test.js 对这四种情形做了完整覆盖;而 proxy-call.js、mock-expectation.js 等源码则揭示了匹配器被统一消费的底层机制。在测试涉及 Symbol 键、协议符号或唯一标识的代码时,用它能让断言更精确、意图更明确。
- 测试
- 开发工具
【免费下载链接】sinon
Test spies, stubs and mocks for JavaScript.
相关推荐
sinon.match.bool 布尔匹配器:用 Sinon 精确校验 Boolean 类型参数
sinon.match.bool 布尔匹配器:用 Sinon 精确校验 Boolean 类型参数 sinon.match.bool 是 Sinon 匹配器(Ma
测试开发工具sinon.match.func 匹配器指南:在 Sinon 中断言函数类型参数
sinon.match.func 匹配器指南:在 Sinon 中断言函数类型参数 sinon.match.func 是 Sinon 匹配器(Matcher)体系
测试开发工具Sinon 匹配器实战:sinon.match.instanceOf 精确校验对象实例类型
Sinon 匹配器实战:sinon.match.instanceOf 精确校验对象实例类型 导读 sinon.match.instanceOf type 是 S
测试开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考