ARTICLE DETAIL

资讯详情

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

Sinon `stub.withArgs()` 深度指南:按参数精准定义 stub 行为与断言

Sinon `stub.withArgs()` 深度指南:按参数精准定义 stub 行为与断言 测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载stub.withArgs(...)是 Sinon 中实现按参数定制行为的核心 API它允许你在同一个 stub 上为不同参数分别定义返回值、异常或回调行为并让每个参数过滤后的 stub拥有独立的调用记录从而写出更精确的断言。本文以 docs/concepts/stubs/api/with-args.md 为骨架结合仓库源码与测试用例系统讲解其匹配规则深度比较、前缀匹配、最具体优先、与 matcher / onCall / Promise 行为的组合方式以及底层实现原理帮助你真正可复制、可落地地在测试中使用它。一、stub.withArgs()是什么按官方文档的定义stub.withArgs()只针对传入的特定参数 stub 该方法。它有两大用途让断言更富表达力——withArgs(...)返回的过滤后 stub与原始 stub 共享调用记录但你可以直接基于这个过滤实例做断言例如stub.withArgs(42).callCount不必在原始 stub 的完整调用历史中手动筛选。让同一个 stub 针对不同参数表现出不同行为——例如参数为1时返回1、参数为42时返回1之外的值、参数为x时抛异常这在模拟复杂接口时极其常用。import * as sinon from sinon; const callback sinon.stub(); callback.withArgs(42).returns(1); callback.withArgs(1).throws(new Error(apple pie)); // 未匹配任何参数的调用无返回值、无异常 callback(); // undefined callback(42); // 1 callback(1); // throws new Error(apple pie)这段示例正是仓库中该文档所引用测试文件的真实内容见 docs/tests/docs/stubs/api/with-args.test.js。二、参数匹配规则深度比较、前缀匹配与最具体胜出1. 对象与数组使用深度比较文档明确说明withArgs对对象和数组使用深度比较deep comparison。在源码中这一逻辑由 src/sinon/spy.js 的matches函数承担它调用sinonjs/samsam的deepEqual进行判定function matches(fake, args, strict) { const margs fake.matchingArguments; if ( margs.length args.length deepEqual(slice(args, 0, margs.length), margs) ) { return !strict || margs.length args.length; } return false; }也就是说传入{ foo: bar }这类对象字面量作为期望参数时调用时传入结构相同但可能是不同实例、甚至含多余键的对象也能命中——只要deepEqual判定相等。如果需要严格引用比较strict comparison请改用stub.withArgs(sinon.match.same(obj))它要求值严格等于ref语义详见 docs/concepts/matchers/api/same.md 与 matcher 总览 docs/concepts/matchers/。2. 前缀匹配期望参数少可命中实参多的调用从matches的第一个条件margs.length args.length可以看出只要调用时的实参数量不少于期望参数数量且前 N 个实参与期望参数深度相等即算匹配非严格模式下。例如const stub sinon.stub(); stub.returns(0); // 兜底行为 stub.withArgs(1).returns(1); stub.withArgs(1, 1).returns(2); stub(); // 0 无参数不匹配任何 withArgs stub(1); // 1 stub(1, 1); // 2 stub(1, 1, 1); // 2 实参更多前缀匹配命中 withArgs(1, 1)这一组合场景在 test/src/stub-test.js 中有对应的测试用例。3. 多个匹配时最具体者胜出当一次调用同时满足多个withArgs条件时例如withArgs(1)与withArgs(1, 1)都匹配stub(1, 1)Sinon 采用匹配参数列表最长者优先的规则。该逻辑位于 src/sinon/stub.js 的functionStubfunction functionStub() { const args slice(arguments); const matchings proxy.matchingFakes(args); const fnStub pop( sort(matchings, function (a, b) { return ( a.matchingArguments.length - b.matchingArguments.length ); }), ) || proxy; return getCurrentBehavior(fnStub).invoke(this, arguments); }所有匹配的 fake 按matchingArguments.length升序排序后取最后一个即参数最多、约束最具体的 fake来执行若一个都不匹配则回退到 stub 本体自身的默认行为。设计直觉约束越具体的定义越优先兜底行为永远在最底层。三、按参数过滤返回值、异常与回调withArgs(...)返回的过滤实例本身就是一个完整的 stub所有行为 API 都可在它之上链式调用const stub sinon.stub().returns(23); // 默认返回值 stub.withArgs(42).returns(99); // 仅参数 42 时返回 99 stub.withArgs(42).throws(); // 仅参数 42 时抛异常与上一条二选一使用 stub(); // 23 stub(42); // 99 或抛异常取决于最后定义的行为仓库测试 test/src/stub-test.js 验证了返回值按参数过滤与异常按参数过滤两种行为未匹配参数的调用不受影响命中参数的调用则应用过滤实例上的定义。对同一组参数多次调用withArgs时由于内部会复用已存在的匹配 fake详见下文源码解析后一次定义会覆盖前一次即以最后定义的行为为准。四、让断言更精确过滤实例拥有独立调用记录withArgs(...)的过滤结果不仅用于定义行为它本身还是一个带完整调用历史的 spy 子实例。你可以直接断言带有某组参数的调用发生了多少次const callback sinon.stub(); callback.withArgs(42).returns(1); callback(42); callback(42); callback.withArgs(42).callCount; // 2 callback.withArgs(42).calledOnce; // false callback.withArgs(42).firstCall; // 第一次命中 42 的调用对象 callback.withArgs(42).calledWith(42); // true测试 docs/tests/docs/stubs/api/with-args.test.js 中就用到了callback.withArgs(42).callCount。这比在原始 stub 的args数组里手工过滤要直观得多也符合文档所述访问与调用相同的 spy的初衷。五、与sinon.match组合模糊匹配withArgs的参数可以是任意sinon.matchmatcher匹配时会按 matcher 语义进行判定。例如用match对象做部分属性匹配const stub sinon.stub().returns(23); stub.withArgs(match({ foo: bar })).returns(99); stub(); // 23 stub({ foo: bar, bar: foo }); // 99 部分匹配命中该行为在 test/src/stub-test.js 中有明确测试。matcher 的完整清单与组合方式sinon.match.any、sinon.match.number、sinon.match.string、sinon.match.same、sinon.match.has等可查阅 docs/concepts/matchers/ 与 docs/concepts/matchers/combining-matchers.md。六、与onCall组合withArgs(...).onCall(...)定义顺序行为withArgs与按调用次序控制的onCall可以组合使用为特定参数的特定次序调用定义行为const stub sinon.stub(); stub.withArgs(42).onFirstCall().returns(1); stub.withArgs(42).onSecondCall().returns(2); stub(42); // 1 stub(42); // 2注意顺序不可颠倒调用stub.onCall(...).withArgs(...)会直接抛错。原因是行为对象behavior上定义的withArgs被显式禁止见 src/sinon/behavior.jswithArgs: function withArgs(/* arguments */) { throw new Error( Defining a stub by invoking stub.onCall(...).withArgs(...) is not supported. Use stub.withArgs(...).onCall(...) to define sequential behavior for calls with certain arguments., ); },对应的回归测试在 test/src/behavior-extra-test.js。牢记官方给出的唯一正确姿势先withArgs过滤参数再onCall指定次序。七、与 Promise 行为组合resolves/rejects及 promiseLibrary 传播withArgs同样适用于异步行为可针对不同参数分别解析或拒绝const stub sinon.stub(); stub.withArgs(ok).resolves(done); stub.withArgs(bad).rejects(new Error(boom)); await stub(ok); // done await stub(bad); // rejects从源码看src/sinon/stub.js 的withArgs在委托给spy.withArgs创建过滤实例后会做一项特殊处理若父 stub 的defaultBehavior配置了自定义promiseLibrary则把该 Promise 实现库传播到过滤实例的defaultBehavior上保证withArgs(...).resolves(...)使用与父 stub 一致的 Promise 库例如配合sinon.stub指定 bluebird 或 Node 原生 Promise 的场景。八、源码级实现解析spy.withArgs内部发生了什么stub.withArgs的底层实现在 src/sinon/spy.js完整流程如下去重先以严格模式调用matchingFakes(args, true)查找已存在、且期望参数与本次完全一致的过滤实例。若找到直接复用并返回它这正是同一参数重复定义行为会覆盖的原因。实例化未命中时通过instantiateFake()创建新的 stub/spy 实例并为其挂上matchingArguments args、parent this同时推入父实例的fakes列表。继承链新实例上的withArgs被重定向回原始 stuboriginal.withArgs确保从过滤实例上再次调用withArgs依然落在同一棵 fake 树上。历史回填遍历父实例已有的调用记录把每个匹配非严格matches的历史调用同步复制到新实例上——包括thisValues、args、returnValues、exceptions、callIds最后通过createCallProperties生成firstCall/secondCall/lastCall等属性。这就是过滤实例在定义之后立即拥有历史调用记录的原因。而在调用侧src/sinon/stub.js 的functionStub负责在每次调用时收集所有匹配的 fake并按匹配参数长度最长者优先选出行为执行见第二节。九、容易踩的坑与最佳实践深度比较 ≠ 引用比较默认对对象/数组做深度比较需要严格语义时用sinon.match.same(obj)。前缀匹配的边界withArgs(1)会命中stub(1, 2)实参更多且前缀相等若希望实参数量也必须完全一致可通过sinon.match或显式列出全部参数来收窄约束。最具体优先多个withArgs同时命中时参数列表最长者生效务必先想清楚兜底行为stub 本体上的returns/throws再叠加过滤规则。onCall(...).withArgs(...)会抛错顺序必须是stub.withArgs(...).onCall(...)。同一参数重复定义 覆盖withArgs(42).returns(1)之后再withArgs(42).returns(2)最终行为是返回2。过滤实例也是断言入口优先用stub.withArgs(...).callCount/calledWith等做参数级断言而非手工遍历stub.args。十、相关 API 导航Stub API 总览与完整方法列表docs/concepts/stubs/api/index.md行为定义returnsreturns.md、throwsthrows.md、resolves/rejectsresolves.md、callsFakecalls-fake.md、yieldsyields.md、callsThroughcalls-through.md按调用次序控制on-call.md 与 on-first-call.md行为与历史重置reset-behavior.md、reset-history.mdMatcher 总览docs/concepts/matchers/index.md关键源码src/sinon/stub.js、src/sinon/spy.js、src/sinon/behavior.js相关测试docs/tests/docs/stubs/api/with-args.test.js、test/src/stub-test.js补充说明withArgs也存在于 mock 期望expectation上用于限定 mock 匹配的参数集见 src/sinon/mock-expectation.js 及 docs/concepts/mocks/api/expectations.md但其语义是过滤断言范围而非定义行为分支与stub.withArgs的用法定位不同使用时注意区分。赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐Sinon 断言 API 完全指南用 sinon.assert 精确验证 spy、stub 与 fake 的行为Sinon 断言 API 完全指南用 sinon.assert 精确验证 spy、stub 与 fake 的行为 Sinon.JS 内置了一套与 fakes测试开发工具DB-GPT MS-RAG 框架深度指南多源检索增强生成的索引架构与 Agentic 检索实战DB GPT MS RAG 框架深度指南多源检索增强生成的索引架构与 Agentic 检索实战 导读 本文以 DB GPT 官方模块文档 docs/doc测试开发工具sinon.match 完全指南为断言与 Stub 提供灵活精准的参数匹配sinon.match 完全指南为断言与 Stub 提供灵活精准的参数匹配 sinon.match 是 sinon 测试工具库中用于 参数匹配 的核心工厂函数测试开发工具上一篇Gatsby配置最佳实践gh_mirrors/v41/v4优化插件顺序与设置下一篇10个CI/CD安全最佳实践构建安全可靠的软件交付流水线创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表