拓冰建站拓冰建站
首页 / 资讯中心 / 正文

深入理解 Sinon 的 `spyCall.firstArg`:读取单次调用首个参数的正确姿势

测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载spyCall.firstArg是 Sinon 中 spy call 对象的一个核心只读属性用于获取某一次函数调用传入的第一个参数值。它适用于 fake、spy、stub 和 mock 方法等所有被 Sinon 代理过的函数是细粒度验证调用行为、提取回调参数时最常用的入口之一。读完本文你将掌握firstArg的语义边界含无参数调用时的表现、获取 spyCall 的多种途径以及它在源码中的实现原理。spyCall.firstArg是什么在 Sinon 的概念体系中spy call 是对一次“被监视函数调用”的对象化表示。当 fake、spy、stub 或 mock 方法 被调用时Sinon 会为每一次调用生成一个独立的 spyCall 对象其中就包含本次调用的全部细节args本次调用的参数数组firstArg本次调用的第一个参数本文主题lastArg本次调用的最后一个参数callback本次调用中作为回调的函数参数returnValue本次调用的返回值exception本次调用抛出的异常thisValue本次调用执行时的this上下文firstArg的官方定义非常简洁——它保存了对该次调用第一个参数的引用参见 first-arg.md。这里的“引用”一词值得注意它直接指向原始参数值本身而非拷贝因此对复杂对象如对象、数组、函数使用时比较的是引用与原始值。获取 spyCall 对象的五种途径要读取firstArg首先需要拿到某个 spyCall 对象。Sinon 提供了以下常用途径1.getCall(n)按索引取第 n 次调用fake.getCall(n)返回第n次调用的 spyCall索引从 0 开始。在 proxy.js 的实现中getCall还支持负数索引——-1表示从最后一次调用往前数const f sinon.fake(); f(a); f(b); f(c); f.getCall(0).firstArg; // a f.getCall(1).firstArg; // b f.getCall(-1).firstArg; // c倒数第一次调用2.firstCall/lastCall首尾调用快捷属性proxy-call-util.js 中的createCallProperties会在每次调用后更新代理上的firstCall、secondCall、thirdCall与lastCall属性const f sinon.fake(); f(apple pie, banana pie); f.lastCall.firstArg; // apple pie f.firstCall.firstArg; // apple pie3.getCalls()批量获取全部调用当需要遍历所有调用逐个检查firstArg时getCalls()返回所有 spyCall 的数组。4.secondCall/thirdCall特定序号调用与firstCall类似secondCall与thirdCall分别指向第二、第三次调用不存在时为null。5.this.args[0]从代理级参数数组取首参代理对象本身也有args所有调用的参数数组的数组f.args[0][0]等价于f.getCall(0).firstArg。但 spyCall 的firstArg语义更直观且不受args二维结构影响。基本行为有参调用与无参调用firstArg的取值规则由创建 spyCall 时的参数个数决定。参照 first-arg.test.js 中的官方测试import t from tap; import sinon from sinon; const f sinon.fake(); // 有参数调用firstArg 为第一个参数 f(apple pie, banana pie); t.equal(f.lastCall.firstArg, apple pie); // 通过 // 无参数调用firstArg 为 undefined f(); t.equal(f.lastCall.firstArg, undefined); // 通过两个关键结论有参数时firstArg精确等于args[0]无参数调用时firstArg为undefined不会抛出异常或返回null。这一点在 fakes 基础用法测试 中也有印证未传参数的 fake 调用后fake.firstArg为undefined。因此断言firstArg是否为undefined可以用来判断某次调用是否携带了参数。结合其他 spyCall 属性完成组合断言firstArg常常需要与args、lastArg等属性配合使用才能准确描述一次调用const spy sinon.spy(); spy(query, { page: 2 }, callback); const call spy.lastCall; call.firstArg; // query首个参数 call.args; // [query, { page: 2 }, callback]完整参数数组 call.args[0]; // 等价于 call.firstArg call.lastArg; // callback最后一个参数特别地lastArg与callback有关联在 proxy-call.js 的创建逻辑中若最后一个参数是函数它同时会成为lastArg和callback。结合 callback 属性文档 可进一步提取回调。在 fake、spy、stub 与 mock 上的统一表现由于 fake、spy、stub 与 mock 方法最终都经由同一套 proxy 机制创建调用记录proxy.js、proxy-invoke.jsfirstArg的行为在四种对象上完全一致// spy包装现有函数 const spy sinon.spy((name) hello ${name}); spy(world); spy.lastCall.firstArg; // world // stub预定义行为 const stub sinon.stub().returns(1); stub(x, y); stub.lastCall.firstArg; // x // fake独立假函数 const fake sinon.fake.returns(42); fake(1, 2); fake.lastCall.firstArg; // 1 // mock 方法 const obj { greet: () {} }; const mock sinon.mock(obj); const expectation mock.expects(greet); obj.greet(hi); expectation.lastCall.firstArg; // hiexpectation 本身也是代理源码级原理firstArg从何而来理解firstArg的底层实现能帮你预判各种边界情况。核心逻辑位于 proxy-call.js 的createProxyCall工厂函数export default function createProxyCall( proxy, thisValue, args, returnValue, exception, id, errorWithCallStack, ) { let firstArg, lastArg; if (args.length 0) { firstArg args[0]; lastArg args[args.length - 1]; } const proxyCall Object.create(callProto); // ... proxyCall.args args; proxyCall.firstArg firstArg; proxyCall.lastArg lastArg; // ... return proxyCall; }由此可以明确实现事实firstArg并非 getter 而是普通实例属性在 spyCall 创建时一次性写入它直接取自调用时记录的args[0]因此是对原参数的引用当args.length 0时firstArg保持undefined局部变量未赋值不会额外处理同理lastArg为args[args.length - 1]两者只在args非空时才有值。调用链完整回放可对照 proxy-invoke.js代理函数被调用invoke(func, thisValue, args)执行调用incrementCallCount并把args、thisValue、callId推入对应数组调用createCallProperties刷新firstCall/lastCall等快捷属性内部调用getCall执行原函数记录返回值/异常再次调用createCallProperties使 spyCall 携带完整的returnValue、exception等。因此每次访问f.lastCall.firstArg实际上都会经由getCall(this.callCount - 1)实时构建一个新的 spyCall 对象并读取其firstArg属性。典型实战场景场景一断言函数以特定参数被调用const sendEmail sinon.spy(); sendEmail(userexample.com, 欢迎注册, body); sinon.assert.calledWith(sendEmail, userexample.com); // 更细粒度直接检查最后一次调用的首个参数 t.equal(sendEmail.lastCall.firstArg, userexample.com);场景二从调用记录中提取回调参数const fake sinon.fake(); fake(1, 2, done); fake.lastCall.firstArg; // 1 —— 但若首参不是回调应改用 callback / lastArg注意firstArg只承诺“第一个参数”不保证它是函数。若需要提取回调应使用 callback 属性它指向最后一个函数参数或yield/yieldTo系列方法。场景三多参数调用中的位置敏感断言const f sinon.fake(); f(GET, /api/users, { limit: 10 }); const call f.getCall(0); call.firstArg; // GET call.args[1]; // /api/users call.args[2]; // { limit: 10 }注意事项与边界情况无参数调用返回undefined判断“是否携带参数”时请用firstArg ! undefined或检查args.length不要依赖 null的宽松判断undefined null为真会与显式传null混淆。显式传入undefined与不传参数不同f(undefined)的firstArg是undefined有参数但值为 undefined而f()的firstArg也是undefined无参数。两者在firstArg上无法区分需要时用args.length区分。与代理级firstArg的区别代理对象上也有firstArg属性初始为null见 proxy.js它记录的是最近一次调用的首参而spyCall.firstArg是某个特定调用的首参二者不要混用。多调用场景下请优先使用lastCall.firstArg或getCall(n).firstArg。重置后失效调用resetHistory或sinon.reset()后调用记录清空lastCall变为null此时访问null.firstArg会抛错应先判空参考 get-call.test.js 中getCall(1) null的断言。相关文档速查spy-call 概念总览spyCall 的定位与getCall说明spyCall API 索引全部方法与属性列表lastArg与firstArg对称的末参属性args完整参数数组spies 入门spy 的创建与使用fakes 入门fake 的创建与使用源码与测试参考proxy-call.jsspyCall 工厂、proxy.jsgetCall、proxy-call-util.js调用属性维护、first-arg.test.js官方行为测试。赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐Sinon 断言系列assert.notCalled 全解析——验证 fake/spy/stub 未被调用的正确姿势Sinon 断言系列 assert.notCalled 全解析——验证 fake/spy/stub 未被调用的正确姿势 assert.notCalled sp测试开发工具Sinon spy.alwaysCalledWithExactly 深入解析如何断言每次调用参数都精确一致Sinon spy.alwaysCalledWithExactly 深入解析如何断言每次调用参数都精确一致 Sinon 是 JavaScript 生态中最常用测试开发工具如何用 pascal update 升级 Pascal Editor 本地运行时并在失败时回滚到旧版本如何用 pascal update 升级 Pascal Editor 本地运行时并在失败时回滚到旧版本 如果你已经在终端里用 Pascal CLI 运行本地 P测试开发工具上一篇Promptify与智能搜索提升搜索引擎的相关性下一篇Ever® Traduora标签系统使用指南如何组织和分类翻译创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门