拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Sinon Mock Expectations 详解:用链式预期声明并验证 JavaScript 方法调用

Sinon Mock Expectations 详解:用链式预期声明并验证 JavaScript 方法调用 测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载在 Sinon 中expectation预期是 mock 的核心构件它是一种带预编程行为类似 stub和预编程预期类似 spy 断言的假方法会在测试中按你预先声明的调用次数、参数与this上下文对方法调用进行即时校验未按预期使用即让测试失败。本文以官方文档 docs/concepts/mocks/api/expectations.md 为骨架结合 src/sinon/mock-expectation.js 与 src/sinon/mock.js 的源码实现系统讲解 expectation 的创建方式、全部链式 API、验证机制与底层原理让你能在单元测试中精准掌控“被测单元”的交互方式。一、Expectation 是什么Mocks及其 expectations是假方法——它们既像 spies 一样记录调用又像 stubs 一样具有预编程行为同时还带有预编程的预期expectations。如果 mock 没有被按预期使用测试就会失败参见 docs/concepts/mocks/index.md。expectation实例同时实现了 spies 与 stubs 两套 API因此它既能记录调用历史也能配置返回值/抛出异常等行为。更重要的是所有 expectation 方法都返回 expectation 实例本身这意味着你可以在一条语句里无限链式调用。文档在开头给出的典型用法如下对应测试见 docs/tests/docs/mocks/api/expectations.test.jsconst mock sinon.mock(obj); mock.expects(ajax).atLeast(2).atMost(5); // 链式预设至少 2 次、至多 5 次 obj.ajax(); // 实际调用 3 次 obj.ajax(); obj.ajax(); mock.verify(); // 验证预期是否满足不满足则抛出异常 mock.restore(); // 恢复被替换的原始方法二、创建 Expectation 的两种方式sinon.expectation.create([methodName])创建一个不依附于任何 mock 对象的 expectation本质上是一个匿名的 mock 函数。methodName是可选参数仅用于让异常消息更易读const expectation sinon.expectation.create(loadUser); expectation.once(); // 预设恰好调用一次 expectation.verify(); // 若从未调用则抛出 ExpectationError从源码看create的实现src/sinon/mock-expectation.js#L72-L78是以 stub 为基础、把mockExpectation上的方法以不可枚举方式扩展进去并记录expectation.method methodNamecreate: function create(methodName) { const expectation extend.nonEnum(stub(), mockExpectation); delete expectation.create; expectation.method methodName; return expectation; }这解释了为什么 expectation 既能链式设置预期又具备 stub 的returns、throws、callsFake等行为能力——它在构造上就是“stub 预期规则”。sinon.mock([methodName])当sinon.mock收到一个字符串或未传参时与sinon.expectation.create完全等价。这一点在 src/sinon/mock.js#L19-L25 中可以直接看到export default function mock(object) { if (!object || typeof object string) { return mockExpectation.create(object ? object : Anonymous mock); } return mock.create(object); }也就是说sinon.mock(someMethod)与sinon.expectation.create(someMethod)返回同一类匿名 mock 函数。而当传入真实对象时mock.create会返回一个 mock 对象不改变原对象本身供你通过mock.expects(methodName)为对象的方法设置预期参见 docs/concepts/mocks/api/_index.md。三、调用次数预期从精确值到区间以下方法用于约束“方法应当被调用多少次”全部返回this以便继续链式调用。方法语义源码实现src/sinon/mock-expectation.jsexpectation.atLeast(number)最少调用number次atLeast校验参数为 number设置minCallsL86-L99expectation.atMost(number)最多调用number次atMost校验参数为 number设置maxCallsL101-L114expectation.never()恰好 0 次委托exactly(0)L116-L118expectation.once()恰好 1 次委托exactly(1)L120-L122expectation.twice()恰好 2 次委托exactly(2)L124-L126expectation.thrice()恰好 3 次委托exactly(3)L128-L130expectation.exactly(number)恰好number次先atLeast(num)再atMost(num)L132-L139几个重要的底层细节默认值是“恰好一次”。mockExpectation的默认状态是minCalls: 1, maxCalls: 1src/sinon/mock-expectation.js#L68-L70。这意味着mock.expects(greet)不带任何次数约束时等价于预设“恰好被调用 1 次”——这一点在测试 docs/tests/docs/mocks/api/expects.test.js 中有直接验证未调用greet就mock.verify()会抛出匹配/Expected greet\(\[...\]\) once \(never called\)/的异常。atLeast/atMost对非数字参数抛出TypeError。例如expectation.atLeast(2)会抛出TypeError: 2 is not number。limitsSet标志首次调用atLeast或atMost时会把另一个限值置为null表示“另一侧无限制”。例如只调用atMost(5)则minCalls被置为null预期变为“最多 5 次、无下限”只调用atLeast(2)则maxCalls为null变为“至少 2 次、无上限”。exactly就是“下上限相等”never/once/twice/thrice都是exactly的语法糖而exactly(num)内部等价于atLeast(num).atMost(num)。验证消息的生成逻辑在expectedCallCountInWordssrc/sinon/mock-expectation.js#L28-L47当最小与最大限值不同时会拼出at least X and at most Y只有单侧限值时输出at least X或at most X。次数则借助timesInWords见 src/sinon/util/core/times-in-words.js转成英文单词。完整示例区间预期const obj { ajax: function () { return response; } }; const mock sinon.mock(obj); mock.expects(ajax).atLeast(2).atMost(5); // 预期 25 次 obj.ajax(); obj.ajax(); obj.ajax(); // 共 3 次在区间内 mock.verify(); // 不抛异常 mock.restore();该示例即 docs/tests/docs/mocks/api/expectations.test.js 的可运行版本。四、参数预期withArgs与withExactArgs参数约束是 expectation 区别于普通 stub 的重要能力——它会在每次真实调用发生时即时校验参数而不仅是 verify 时。expectation.withArgs(arg1, arg2, ...)预期方法“以给定参数可能还有额外参数被调用”。即只要调用时传入的参数以预期参数开头且逐个深度相等即可允许调用方传入更多参数。支持在参数位置使用 matchers如sinon.match.number。expectation.withExactArgs(arg1, arg2, ...)预期方法“仅以给定参数、无其他参数被调用”。它在withArgs基础上额外设置了expectsExactArgCount true调用时参数个数必须与预期完全一致。源码实现src/sinon/mock-expectation.js#L259-L268withArgs: function withArgs() { this.expectedArguments slice(arguments); return this; }, withExactArgs: function withExactArgs() { this.withArgs.apply(this, arguments); this.expectsExactArgCount true; return this; },重要约束一个 expectation 实例只保存一组通过withArgs/withExactArgs指定的参数。后续调用会覆盖之前指定的参数集合即使参数不同。因此文档明确建议每个测试用例中对同一个 expectation 不要多次调用这两个方法。参数校验的底层逻辑每次被 mock 的方法真正被调用时verifyCallAllowedsrc/sinon/mock-expectation.js#L145-L215会依次检查是否超出最大调用次数若receivedMaxCalls为真立即失败并提示already called N timesthis上下文是否匹配如果设置了expectedThis是否设置了expectedArguments若调用无参数而预期有参数失败参数个数args.length expectedArguments.length视为“参数太少”若expectsExactArgCount且个数不等视为“参数太多”逐参数校验先判断预期参数是否为 matcher用match.isMatcherpossibleMatcher.test(arg)再对非 matcher 参数执行deepEqual来自sinonjs/samsam深度相等比较任一不匹配即失败。allowsCallsrc/sinon/mock-expectation.js#L217-L257则提供“只判断、不抛异常”的版本供 mock 在多个 expectation 之间做匹配选择时使用。五、上下文预期expectation.on(obj)expectation.on(obj)预期方法以obj作为this被调用。源码实现极简src/sinon/mock-expectation.js#L270-L273on: function on(thisValue) { this.expectedThis thisValue; return this; }设置后verifyCallAllowed中的expectedThis in this this.expectedThis ! thisValue检查即生效若实际调用时this不是obj会立即失败并给出类似method called with X as thisValue, expected Y的消息。典型应用场景是验证方法是否以特定对象为接收者被调用例如const controller { name: AppController }; const mock sinon.mock(controller); mock.expects(handle).on(controller).once(); // 必须以 controller 为 this 调用 controller.handle(); mock.verify();六、验证与失败expectation.verify()expectation.verify()验证预期是否满足不满足则抛出异常。其实现src/sinon/mock-expectation.js#L299-L318verify: function verify() { if (!this.met()) { mockExpectation.fail(String(this)); } else { mockExpectation.pass(String(this)); } return true; }, fail: function fail(message) { const exception new Error(message); exception.name ExpectationError; throw exception; }关键点met()src/sinon/mock-expectation.js#L141-L143判定规则是!this.failed receivedMinCalls(this)——即之前未发生过违规调用、且调用次数达到minCalls下限。注意receivedMinCalls只在minCalls为数字时生效这正是atMost单独使用时minCalls为null不设下限的原因。失败时抛出的是name ExpectationError的普通Error消息由toString()src/sinon/mock-expectation.js#L275-L297生成格式类似未满足Expected greet([...]) once (never called)满足Expectation met: greet([...]) once带参数时withExactArgs会精确展示参数withArgs则追加[...]表示“允许更多参数”。verify()正常通过时返回true可以写进断言链。与mock.verify()的关系单个 expectation 的verify()只校验自身而mock.verify()src/sinon/mock.js#L94-L118会遍历该 mock 下所有 proxy 的全部 expectation收集所有未满足的预期消息然后自动调用restore()恢复所有被替换的方法最后若存在失败消息则抛出一个汇总了全部失败与已满足预期的异常。测试 docs/tests/docs/mocks/api/verify-1.test.js 验证了verify()后原方法的restore属性会被清除即方法已恢复原状。因此实战中通常只需要调用一次mock.verify()完成“验证 恢复”两件事docs/concepts/mocks/api/verify.mdmock.restore()亦可单独调用docs/concepts/mocks/api/restore.md。七、Expectation 如何与 Mock 协作源码级调用链理解 expectation 在真实调用中如何生效需要看mock.expects与invokeMethod的配合src/sinon/mock.jsmock.expects(method)L55-L82首次为某方法设置预期时用wrapMethodsrc/sinon/util/core/wrap-method.js把原方法替换为代理随后为每个expects调用创建一个 expectation 并压入this.expectations[method]数组。因此同一个方法可以挂多个 expectation不同参数/次数组合。真实调用发生时代理回调mockObject.invokeMethod(method, this, arguments)L120-L203先从该方法的 expectation 列表中筛出参数匹配的arrayEquals结合expectsExactArgCount再从其中找出“未满足且允许本次调用”的!met() allowsCall(...)优先应用其 stub 行为若没有任何 expectation 允许本次调用则记录Unexpected call并抛异常。verify()时逐条检查expectation.met()汇总消息。这条链路说明expectation 的校验发生在每次调用瞬间而非仅 verify 时——一旦调用次数超过maxCalls、参数不匹配或this错误异常会立刻抛出这正是 mock “即时验证交互”的设计意图docs/concepts/mocks/index.md 中“Use mocks when you need to verify interactions immediately upon use”。八、完整实战一个可运行的链式预期用例综合以上全部 API写一个覆盖次数、参数、this与验证的完整用例可直接在安装 sinon 的项目中运行const sinon require(sinon); const api { request: function (path, payload) { return GET ${path}; } }; // 1. 为对象创建 mock const mock sinon.mock(api); // 2. 链式预设预期恰好一次、以 api 为 this、精确参数 mock.expects(request) .once() .on(api) .withExactArgs(/users, { id: 42 }); // 3. 以符合预期的方式调用 api.request(/users, { id: 42 }); // 4. 验证并自动恢复 mock.verify(); // 满足预期返回 true同时恢复 request 原方法若把第 3 步改为api.request(/users)参数个数不足调用瞬间就会抛出ExpectationError消息类似于request received too few arguments ([ /users ]), expected [ /users, { id: 42 } ]——这正是 expectation 即时校验能力的体现。九、使用建议与注意事项结合 docs/concepts/mocks/index.md 的官方建议使用 expectation 时应注意只为被测方法使用 mockmock 自带预期、会强制实现细节一条经验法则是“如果你不会为某次具体调用添加断言就不要 mock 它改用 stub”单个测试中最多使用一个 mock可含多个 expectation避免测试耦合过深withArgs/withExactArgs每个 expectation 只调用一次后续调用会覆盖之前的参数集合文档同时建议优先考虑 fakesdocs/concepts/fakes/index.md如果只是需要简单的行为替换fakes 配合显式断言更不易过度耦合mocks 适合“预期先行、交互即时验证”能真正澄清测试意图的场景默认恰好一次mock.expects(m)未加次数约束时默认minCalls maxCalls 1验证前务必确认实际调用次数符合预期。如果想深入 expectation 的完整 API 与 mock 的其他方法expects、restore、verify、expectations属性可继续阅读 docs/concepts/mocks/api/expectations.md、docs/concepts/mocks/api/expects.md 及 docs/concepts/mocks/api/_index.md。赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐快速上手 skill-installer一键安装与管理 Codex 技能快速上手 skill installer一键安装与管理 Codex 技能 手动 clone 仓库、翻目录、复制技能文件夹再重启 Codex装一个技能要折腾四测试开发工具Mockery 期望声明Expectation Declarations完整指南从方法调用约束到调用次数验证Mockery 期望声明Expectation Declarations完整指南从方法调用约束到调用次数验证 导读 本文以 Mockery 官方参考文档测试开发工具Sinon spyCall.calledWithMatch 详解用匹配器精确验证 spy 调用的实参Sinon spyCall.calledWithMatch 详解用匹配器精确验证 spy 调用的实参 本文围绕 Sinon 中 spyCall.calledW测试开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表