Sinon 断言指南:`assert.called`——验证 spy、stub 与 fake 至少被调用过一次
测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载assert.called(spy)是 Sinon 内置断言 API 中最基础的断言之一只要传入的fake、spy或stub在测试期间至少被调用过一次断言即通过否则抛出一个带有详细描述信息的AssertError。本文基于当前仓库中 called.md 文档并结合 assert.js 源码与对应的 单元测试从使用方式、错误消息、底层实现到相关断言家族完整讲解这一断言在测试中的定位与实战用法。断言签名与语义assert.called(spy);语义非常直接校验目标函数是否被调用过至少一次。只要调用次数 ≥ 1断言通过调用次数为 0断言失败。它适用于 Sinon 三类测试替身test doublespy包装原始函数或独立存在记录调用信息但不改变行为stub可预设行为的替身同样记录调用信息fake更现代、更轻量的独立替身与 spy 拥有相同的调用记录能力。因此凡是能记录调用历史的 Sinon 代理对象都可以作为assert.called的参数。快速上手在 ESM 环境下从sinon包导入 API 后即可使用参见 called.md 中的基础示例import * as sinon from sinon; const spy sinon.spy(); // 此时 spy 尚未被调用断言失败 sinon.assert.called(spy); // Error [AssertError]: expected spy to have been called at least once but was never called spy(); // 调用一次 // 现在断言通过不产生任何异常 sinon.assert.called(spy);关键行为有两点失败即抛错断言失败时抛出的异常name为AssertError见 assert.js 中fail的实现可以直接被测试框架捕获并判定用例失败成功静默断言通过时不返回任何值、不产生异常配合assert.pass的内部机制静默返回见 assert.js。在测试框架中使用仓库为每个文档 API 都配套了可运行的测试用例。assert.called对应的测试位于 docs/tests/docs/assertions/api/called.test.js使用tap编写覆盖了调用后通过与未调用即失败两个方向import tap from tap; import * as sinon from sinon; tap.test(assert.called - passes when spy was called, (t) { const spy sinon.spy(); spy(); t.doesNotThrow(() { sinon.assert.called(spy); }, assertion should pass); t.end(); }); tap.test(assert.called - fails when spy was not called, (t) { const spy sinon.spy(); t.throws( () sinon.assert.called(spy), /expected spy to have been called at least once but was never called/, assertion should fail with descriptive message ); t.end(); });这套测试同时也验证了两件事失败消息的文本是稳定可匹配的契约正则中直接匹配了错误文案以及断言通过时确实不会抛出异常。在实际的 Jest / Mocha / Vitest 项目中可直接把断言放进it/test回调中失败时框架会自动收集AssertError。失败时的错误消息是如何生成的assert.called的默认失败消息模板定义在 assert.jsmirrorPropAsAssertion( called, expected %n to have been called at least once but was never called, );其中%n是格式化占位符由 spy-formatters.js 中的n处理器替换为spyInstance.toString()即 spy 的名称描述如spy。消息组装完成后通过(fake.printf || fake.proxy.printf).apply(...)渲染出来见 assert.js最终抛出。与它同族的断言还有calledOnce、calledTwice、calledThrice它们会额外用%c占位符输出实际的调用次数英文描述如once、twice、thrice由timesInWords生成例如expected spy to be called once but was called twicecalledOnce 失败时也就是说assert.called的失败消息聚焦于从未被调用这一种失败形态而次数敏感型断言如calledOnce则能进一步告诉你实际被调用了几次。源码视角assert.called的底层实现assert.called并不是手写的一个独立函数而是通过mirrorPropAsAssertion这个工厂函数批量生成的见 assert.js。其执行流程如下校验参数verifyIsStub(fake)assert.js先确认传入的是有效的 Sinon 代理对象——若传入null会报fake is not a spy若对象没有getCall方法会报fake is not stubbed校验参数个数verifyIsValidAssertionassert.js规定called不接受额外参数多传参数会直接报错called takes 1 argument but was called with N arguments读取布尔属性对called而言meth未提供函数因此直接读取fake.called这个布尔属性assert.js判定与输出failed为真时调用failAssertion渲染并抛出AssertError否则调用assert.pass静默通过。fake.called属性由谁维护assert.called读到的called布尔属性是 Sinon 代理对象在每次被调用时由incrementCallCount同步更新的见 proxy-call-util.jsexport function incrementCallCount(proxy) { proxy.called true; proxy.callCount 1; proxy.notCalled false; proxy.calledOnce proxy.callCount 1; proxy.calledTwice proxy.callCount 2; proxy.calledThrice proxy.callCount 3; }代理在初始化时called被置为false见 proxy.js此后每调用一次就置true并递增callCount。因此assert.called本质上是对spy.called true这一状态的断言封装——你也可以在代码里直接读取spy.called做条件判断但用assert.called能获得统一、描述清晰的失败消息。与相关断言的组合使用assert.called只回答有没有被调用过这一个问题。当测试需要更强的约束时可以按需组合 Assertions API 家族中的其他成员完整列表见 docs/concepts/assertions/api/场景推荐断言语义至少调用一次assert.called本次讨论的断言一次都没调用assert.notCalled与called互斥失败消息为expected spy to not have been called but was called %c%C恰好调用一次assert.calledOnce失败时会输出实际次数恰好两次 / 三次assert.calledTwice/assert.calledThrice次数精确断言精确参数assert.calledWith/assert.calledWithExactly结合参数校验以 matcher 匹配参数assert.calledWithMatch结合 matchers 使用精确调用次数assert.callCount断言具体次数数值一个常见组合是先确认被调用再确认调用参数const spy sinon.spy(); doWork(spy); sinon.assert.called(spy); // 1. 至少调用了一次 sinon.assert.calledWith(spy, key, 42); // 2. 且最后一次调用参数正确注意assert.called本身不校验参数、不校验this上下文、不校验调用次数上限这些分别由calledWith、calledOn、calledOnce等断言承担按需组合即可。进阶实践与注意事项1. 包装真实方法后再断言除了独立的sinon.spy()最常见的是对真实对象方法做包装验证某个依赖是否被触发const service { save(data) { /* 真实逻辑 */ } }; const saveSpy sinon.spy(service, save); service.save({ id: 1 }); sinon.assert.called(saveSpy); saveSpy.restore(); // 记得恢复原始方法2. 与 stub 一起使用stub 同样记录调用信息因此assert.called也可用于验证某个被替换的依赖是否被调起const stub sinon.stub().returns(42); compute(stub); sinon.assert.called(stub);3. 配合assert.expose批量暴露到测试对象如果希望断言以更贴近阅读习惯的形式出现可以使用assert.expose把整个断言对象批量挂载到目标对象上见 assert.js 的expose实现支持prefix与includeFail选项。例如挂到全局后即可写assert.called(spy)而无需每次sinon.assert.前缀。4. 在沙箱sandbox中使用配合sinon.createSandbox()可以把替身与断言放在同一作用域内统一恢复const sandbox sinon.createSandbox(); const spy sandbox.spy(); run(); sandbox.assert.called(spy); sandbox.restore();5. 注意事项只验证至少一次若测试要求恰好一次请改用calledOnce否则调用两次时assert.called依然静默通过可能掩盖回归不接受额外参数assert.called(spy, somethingElse)会直接抛错而非忽略多余参数参数必须是 Sinon 代理普通函数或非 spy 对象会触发fake is not a spy/fake is not stubbed等前置校验错误错误类型为AssertError断言失败抛出的是名为AssertError的普通Error可被任何主流测试框架识别若开启了shouldLimitAssertionLogs选项长日志会被截断到assertionLogLimit默认 10K见 assert.js。小结assert.called是 Sinon 断言体系里最朴素、最常用的一环它把目标替身是否被调用过这一高频测试诉求封装成一个失败消息清晰、行为可预期的断言。理解它的语义边界只验证至少一次、底层数据来源proxy.called与incrementCallCount以及同族断言calledOnce、notCalled、calledWith等的分工能帮助你在编写单元测试时快速选出正确的断言写出既严谨又易读的测试代码。更多断言方法与使用示例可继续阅读 Assertions API 索引 及仓库中的 断言测试目录。赞分享测试开发工具【免费下载链接】sinonTest spies, stubs and mocks for JavaScript.项目地址https://gitcode.com/gh_mirrors/si/sinon点击查看免费下载相关推荐notebooklm-py 的 Android gRPC 能力与签名取证体系APK 静态提取、Web 端签名推断与移动端实证验证notebooklm py 的 Android gRPC 能力与签名取证体系APK 静态提取、Web 端签名推断与移动端实证验证 本文解析 notebookl测试开发工具Sinon assert.calledThrice 详解精确断言 spy/fake/stub 恰好被调用三次Sinon assert.calledThrice 详解精确断言 spy/fake/stub 恰好被调用三次 sinon.assert.calledThric测试开发工具Sinon assert.alwaysCalledWith 完全指南验证 fake/spy/stub 每次调用参数一致Sinon assert.alwaysCalledWith 完全指南验证 fake/spy/stub 每次调用参数一致 sinon.assert.always测试开发工具上一篇如何在Krita中3分钟实现AI绘画免费开源的终极创作神器下一篇3个资源捕获痛点猫抓浏览器扩展如何重新定义网页媒体下载体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Swagger Codegen 生成的 Jersey2 Java8 客户端:StoreApi 订单与库存接口完整使用指南

Swagger Codegen 生成的 Jersey2 Java8 客户端:StoreApi 订单与库存接口完整使用指南

开发工具代码生成API设计 【免费下载链接】swagger-codegen swagger-codegen contains a template-driven engine to generate documentation, API clients and server stubs in different languages by parsing your OpenAPI / Swagger definition. 项目地址: http…

2026/9/24 16:26:30 阅读更多 →
ARA Research Manager 会话协议深度解析:让 AI 研究 Agent 拥有可审计的跨会话记忆

ARA Research Manager 会话协议深度解析:让 AI 研究 Agent 拥有可审计的跨会话记忆

AI 技能人工智能大模型深度学习 【免费下载链接】AI-Research-SKILLs Comprehensive open-source library of AI research and engineering skills for any AI model. Package the skills and your claude code/codex/gemini agent will be an AI research agent with full hor…

2026/9/24 16:26:30 阅读更多 →
ToastFish:把 Windows 通知栏变成免费背单词神器,摸鱼 3 分钟入门

ToastFish:把 Windows 通知栏变成免费背单词神器,摸鱼 3 分钟入门

ToastFish:把 Windows 通知栏变成免费背单词神器,摸鱼 3 分钟入门 【免费下载链接】ToastFish 一个利用摸鱼时间背单词的软件。 项目地址: https://gitcode.com/GitHub_Trending/to/ToastFish 如果你也常想背单词,却怕屏幕上明晃晃的背…

2026/9/24 16:26:30 阅读更多 →

最新新闻

MySQL高负载I/O故障全链路排查与优化实战

MySQL高负载I/O故障全链路排查与优化实战

凌晨两点十六分,监控大屏上的MySQL IOPS曲线突然拉成一条垂直的直线,告警声把值班室的安静撕得粉碎。那条从10点开始缓慢抬升的紫色线条,在那一刻直接冲上了磁盘性能的上限刻度,数据库的活跃会话数同步飙到400,大量业务…

2026/9/24 19:49:18 阅读更多 →
企业级数据库工具怎么选?Navicat与NineData对比分析

企业级数据库工具怎么选?Navicat与NineData对比分析

做过十来年数据库运维和研发,Navicat 基本是很多人电脑里的“标配”,从连 MySQL、Oracle 到日常看数据、导数据,确实顺手。但这两年我越来越多地被企业客户问到同一个问题:团队规模上来之后,Navicat 还够用吗&#xff…

2026/9/24 19:49:18 阅读更多 →
MySQL高负载I/O故障根因分析:从系统层到InnoDB的排查与优化

MySQL高负载I/O故障根因分析:从系统层到InnoDB的排查与优化

这事发生在上个月,客户的线上MySQL实例连续两天在业务高峰时段崩溃报警,从应用侧看就是大量请求超时,接口P99延迟从原本的80ms直接飙到3s以上。我看了一眼监控面板,CPU 80%以上,磁盘I/O util触顶100%,iowai…

2026/9/24 19:49:18 阅读更多 →
C++与Python混合编程:pybind11、ctypes、C API选型指南

C++与Python混合编程:pybind11、ctypes、C API选型指南

1. 混合编程的选型困局:为什么三种方案总让人纠结做C和Python混合开发的人,几乎都绕不开一个灵魂拷问:到底用pybind11、ctypes还是Python C API?我最早接触这个领域是做量化回测系统,核心撮合引擎用C写,策略…

2026/9/24 19:49:18 阅读更多 →
MySQL主从架构下MaxScale读写分离与高可用实战指南

MySQL主从架构下MaxScale读写分离与高可用实战指南

搞数据库的兄弟应该都有过这种体验:主从架构搭好了,读写分离却迟迟没落到位。业务代码里手动判断哪个库写、哪个库读,刚开始还行,等Server挂了一台、主从切换过几轮之后,各种连接串了、事务跑飞、延迟把从库拖垮的问题…

2026/9/24 19:49:18 阅读更多 →
MySQL 1251 报错根源与修复:认证插件兼容性实战指南

MySQL 1251 报错根源与修复:认证插件兼容性实战指南

上周五有个朋友发来一张 MySQL 报错截图: ERROR 1251 (08004): Client does not support authentication protocol requested by server; consider upgrading MySQL client 。他说密码确认了好几遍没问题,3306 端口也是通的,但不管是 Navic…

2026/9/24 19:48:17 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →