Mockery 处理 final 类与 final 方法Proxy Mock 代理 Mock 原理与 Laravel 测试实战【免费下载链接】sql-server-samplesAzure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge项目地址: https://gitcode.com/gh_mirrors/sq/sql-server-samples在 PHP 单元测试中final关键字是 Mockery 这类 Mock 框架最头疼的限制之一它禁止子类覆盖方法而 Mockery 恰恰依赖生成子类来实现 Mock。本文基于本仓库 Laravel 示例samples/development-frameworks/laravel所携带的 Mockery 文档final_methods_classes.rst完整讲解 Mockery 应对 final 类与 final 方法的代理 MockProxy Mock方案何时可用、何时受限、底层是如何实现的以及如何在 Laravel 项目中安全落地。读完本文你将掌握面对final class/final method时最稳妥的 Mock 策略并能从源码与测试层面验证其行为边界。为什么final是 PHP Mock 框架的天敌Mockery 生成 Mock 对象的基本手段是子类化subclassing为被 Mock 的类生成一个子类并覆盖其中的方法从而拦截调用、注入预期行为expectations。这决定了两个硬约束被final修饰的类不能被继承因此无法生成子类来覆盖方法被final修饰的方法不能在子类中被重写因此即使类本身可继承该方法也无法被替换。final关键字的价值在于类型安全与防篡改但这与运行时替换行为的 Mock 需求天然冲突。因此Mockery 官方文档给出的最简单解决方案是不要将类或方法标记为final。但在类型安全与 Mock 能力之间必须妥协时Mockery 提供了Proxy Mock代理 Mock作为折中方案。Proxy Mock把实例对象传给\Mockery::mock()当目标类本身被标记为final或类中包含final方法时不能通过类名生成子类但可以传入一个已实例化的对象来创建代理 Mock// 传类名类为 final 时会抛异常详见下文源码分析 // $mock \Mockery::mock(SomeFinalClass); // 抛出 Mockery\Exception // 传实例对象生成 Proxy Mock一切照常工作 $mock \Mockery::mock(new SomeFinalClass); // 对代理 Mock 设置预期与普通 Mock 完全一致 $mock-shouldReceive(foo)-andReturn(baz); $result $mock-foo(); // baz其工作原理是Mockery 不再继承目标类而是生成一个Proxy代理指向你传入的真实对象对于设置了预期的方法调用代理拦截并返回预期结果对于未设置预期的方法调用代理将调用转发reroute给真实对象执行。关键代价代理 Mock 不继承被 Mock 对象的类类型即不会通过instanceof检查。若代码中存在类型约束typehint例如函数签名要求SomeFinalClass $obj传入代理 Mock 将失败。这正是文档中Mocking functionality 与 type safety 之间的 compromise妥协的含义。在 Laravel 测试中的典型用法在 Laravel 项目中当需要 Mock 一个来自第三方包、被final修饰的服务类时可以通过容器绑定实例$realService new FinalPaymentGateway($config); $proxy \Mockery::mock($realService); $proxy-shouldReceive(charge)-once()-andReturn(true); $this-app-instance(FinalPaymentGateway::class, $proxy); // 此时业务代码中的 $this-app-make(...) 会拿到代理 Mock需要注意的是若业务代码对FinalPaymentGateway有构造器类型约束constructor typehint代理 Mock 因不满足instanceof而无法注入——此时应优先考虑重构去掉final或引入接口而不是强行 Mock。源码级原理Mockery 如何拒绝与退让本仓库的 Mockery 源码library/Mockery清晰展示了这套策略的完整实现链。1. 类为final直接抛异常并提示代理方案在 MockConfiguration.php 的getTargetClass()中当按类名解析目标类时一旦发现其为final且没有传入实例对象立即抛出异常if ($this-getTargetObject() false $dtc-isFinal()) { throw new \Mockery\Exception( The class . $this-targetClassName . is marked final and its methods . cannot be replaced. Classes marked final can be passed in . to \Mockery::mock() as instantiated objects to create a . partial mock, but only if the mock is not subject to type . hinting checks. ); }注意异常信息中的三个要点① 按类名 Mockfinal类被明确拒绝② 补救方式是传入已实例化对象③ 前提是不涉及类型检查——即无法通过instanceof/typehint与文档结论完全一致。2. 生成阶段final类跳过extends在代码生成管线 ClassPass.php 中Pass 负责把生成的 Mock 代码从implements MockInterface改写为extends \TargetClass implements MockInterface但当目标类是final时直接短路返回不生成继承关系if ($target-isFinal()) { return $code; }这正是代理 Mock 无法通过instanceof检查的根因——生成的 Mock 类与目标类之间根本没有继承链。3. 方法筛选final方法被直接排除在 MockConfiguration.php 的getMethodsToMock()中所有候选方法先经过一轮过滤foreach ($methods as $key $method) { if ($method-isFinal()) { unset($methods[$key]); } }final方法不会进入MethodDefinitionPassMethodDefinitionPass.php的方法覆盖清单因此对含final方法的类类本身非final仍可通过类名 Mock只是final方法保持原样——这对应文档与源码中自动生成的内部 partialinternal partial场景。4. 实例对象如何进入 Mock 配置在 Container.php 的参数解析中当传入的参数是对象时它被当作 partial 目标加入 builder} elseif (is_object($arg)) { $partial array_shift($args); $builder-addTarget($partial); continue; }随后 MockConfiguration.php 的getTargetObject()返回该真实对象isInstanceMock()L400-L403标记其为实例 Mock并驱动 InstanceMockPass.php 在生成的类中注入构造逻辑从关联的真实对象拷贝属性、克隆其预期expectations实现代理转发的基础设施。5. 运行时未匹配预期的调用转发给真实对象在 Mock 基类 Mock.php 中保存真实对象的字段注释直接点明其职责/** * Instance of a core object on which methods are called in the event * it has been set, and an expectation for one of the objects methods * does not exist. This implements a simple partial mock proxy system. */ protected $_mockery_partial null;调用未命中任何预期时运行时分派逻辑会走_mockery_deferMissing/_mockery_ignoreMissing分支Mock.php对代理 Mock 而言未设置预期的调用被延后defer到真实对象执行从而让只 Mock 几个方法、其余照常的局部 Mock 语义成立。测试用例验证行为边界的实证仓库测试 ContainerTest.php 用一组针对性用例锁定了这些行为可直接作为行为契约阅读场景 Afinal类按类名 Mock 抛异常L260-L263配合测试夹具final class MockeryFoo3L1377-L1383public function testMockingAKnownConcreteFinalClassThrowsErrors_OnlyPartialMocksCanMockFinalElements() { $m $this-container-mock(MockeryFoo3); // 抛 \Mockery\Exception }场景 Bfinal类可通过传入实例创建代理 Mock且不通过instanceofL273-L279public function testFinalClassesCanBePartialMocks() { $m $this-container-mock(new MockeryFoo3); $m-shouldReceive(foo)-andReturn(baz); $this-assertEquals(baz, $m-foo()); $this-assertFalse($m instanceof MockeryFoo3); // 关键断言不是同一类型 }场景 C含final方法的类可用类名 Mock也可代理 MockL298-L322夹具为class MockeryFoo4含final public function foo()L1385-L1396// 代理 partial传入实例foo() 是 final 但仍可被 Mock因为代理不受 final 限制 $m $this-container-mock(new MockeryFoo4); $m-shouldReceive(foo)-andReturn(baz); $this-assertEquals(baz, $m-foo()); $this-assertEquals(bar, $m-bar()); // 未 Mock 的方法转发给真实对象 $this-assertTrue($m instanceof MockeryFoo4); // 类本身非 final仍满足 instanceof // 传统 partialMockeryFoo4[bar] 可 Mock bar() $m $this-container-mock(MockeryFoo4[bar]); // 但 MockeryFoo4[foo] 无法 partial 化 final 方法 // 期望被忽略foo() 仍返回真实实现 baz $m $this-container-mock(MockeryFoo4[foo]); $m-shouldReceive(foo)-andReturn(foo); $this-assertEquals(baz, $m-foo()); // final 方法不可覆盖断言返回真实值场景 C 揭示了三个层次的结论代理 Mock 能间接 Mockfinal方法因为代理类与目标类无关传统 partial 对final方法的期望会被静默忽略对应getMethodsToMock()的过滤逻辑类本身非final时即使走代理instanceof依然成立。场景 DSPL 内置类同样适用L281-L296SplFileInfo含 public final 方法仍可按类名 Mock 且满足instanceof SplFileInfo——这正是内部自动 partial策略含final方法的类final方法保持原样、其余方法可 Mock的典型代表。这也是 partial_mocks.rst 中第四种内部 partial所指的场景。与 Partial Mock 体系的关系本主题与 Mockery 的 Partial Mock 机制紧密相关详见仓库文档 partial_mocks.rst。Mockery 共提供四种 partial 策略其中三种面向用户策略用法是否受final影响instanceof 是否成立传统 Partial\Mockery::mock(MyClass[foo,bar])final方法无法被 partial 化成立类非 final 时被动 Partial\Mockery::mock(MyClass)-makePartial()同左成立代理 PartialProxy Mock\Mockery::mock(new MyClass)可绕过final限制不成立第四种为框架内部自动生成当你按类名 Mock 一个含final方法的类时Mockery 自动将这些方法排除在覆盖范围外形成内部 partial无需也无法手动指定。若确实必须 Mock 某个final方法代理 Mock 是唯一途径且必须接受instanceof失效的代价。工程实践建议与限制总结在 Laravel或其他 PHP 项目中使用 Mockery 面对final代码时建议按以下顺序决策首选移除final。若类/方法没有硬性安全需求去掉final是最简单、最无副作用的方案Mockery 文档亦明确推荐次选引入接口。将final类的公共行为抽象为接口让业务代码依赖接口而非具体类测试时 Mock 接口即可兼顾类型安全与可测性兜底代理 Mock。对无法修改的第三方final类使用\Mockery::mock($instance)创建代理但务必确认被测代码没有对该类的构造器/参数类型约束否则注入会失败避免不要指望对final方法的期望能被传统 partial 尊重——它会被静默忽略且不会报错容易造成测试看似通过、实际未生效的假象。适用前提与限制本文所述行为基于本仓库 vendor 目录中携带的 Mockery 版本samples/development-frameworks/laravel/vendor/mockery/mockery其composer.json定义了版本约束README 位于 README.md。核心结论——代理 Mock 可 Mockfinal类/方法但不满足instanceof——由源码 MockConfiguration.php 与测试 ContainerTest.php 共同保证是稳定且可验证的行为契约。【免费下载链接】sql-server-samplesAzure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge项目地址: https://gitcode.com/gh_mirrors/sq/sql-server-samples创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
一文搞懂重心的性质 3步搞定重心性质入门到精通 告别报错Stack Trace 昨晚跑代码,满屏红色的 Stack Trace 像天书一样糊脸,盯着 NullPointerException 或 IndexOutOfBounds… · 2026/9/23 16:26:23
3个坑搞定自闭症基因数据解析源码 3个坑搞定自闭症基因数据解析源码 看了一堆教程还是不会写项目?别怪你笨,是那些教程只给你看 API 调用,没带你钻进代码深处。真正的技术壁垒,藏在 源码解析 里。今天咱们不聊虚的,直接拿 GitHub 上最火的生物信息学工具 GATK… · 2026/9/23 16:26:10
从占位符标题到高质量博文:一套完整的内容拆解四步法 拿到一个标题,只有三个字符加三个数字:title888。如果是别人,可能随手当垃圾占位符丢掉。但我干内容这行久了,反而觉得这种编号型标题特别有意思——它代表一个内容还没有被定义,而你恰好被要求把它定义出来。这篇文章… · 2026/9/23 16:26:09
2026 招聘测评哪家靠谱?衡识人才测评等 TOP10 深度评测 一、2026招聘测评市场:从“看报告”转向“看落地”2026年的人才评价逻辑正在变化。据行业调研,招聘中求职者较可信的三样凭证是工作经历、项目成果与学历,但仅有经历和成果不够,企业更需要工具预测一个人未来能否胜任、能否留得住… · 2026/9/25 15:47:03
Codeg多智能体协作:@提及一键委托,Claude Code起草Codex审查并行工作完全指南 Codeg多智能体协作:提及一键委托,Claude Code起草Codex审查并行工作完全指南 【免费下载链接】codeg Collaborative multi-agent AI coding workspace: aggregate sessions from Claude Code, Codex, OpenCode, Pi, Grok Build, etc. Desktop app, self-… · 2026/9/25 15:46:51
Atlas 300V部署YOLO全流程实战:从环境搭建到性能调优 Atlas 300V部署YOLO这件事,网上问的人多,能一次跑通的人少。不少朋友拿到卡之后,第一反应是"这不就是个显卡吗",然后照着CUDA那套习惯去搞,结果连设备都识别不到。我去年在项目里用Atlas 300V Pro 24G做视频… · 2026/9/25 15:46:51
昇腾Atlas 300V实战:从零部署YOLO模型的完整链路 先说结论:Atlas 300V 24G 这块卡,我去年在一个工业质检项目里用了整整五个月。当时团队里有同事第一次接触昇腾平台,拿着 YOLOv5 的权重文件就准备直接往卡上怼,结果卡在模型转换这一步耗了三天。这篇文章就把从零开始部署 YOLO 系… · 2026/9/25 15:46:32
V100显卡sxm2版本引脚定义逆向 最近项目开发看到网上V100显卡sxm2版本引脚定义资源太少,就整理写一篇详细的V100显卡sxm2版本引脚定义也是帮助各大开发者快速入门:废话不多说,看下列图:本篇简单扼要,但是干货满满,以上两图都是我自己ecex… · 2026/9/25 15:46:20
Arria 10 10AX027 核心板适合哪些高速数据处理项目 高速数据处理系统常常同时面对三类压力:前端持续输入、算法实时处理和后端快速输出。只看 FPGA 逻辑资源,很容易忽略 DDR 带宽、PCIe 通道、高速收发器和底板走线的限制。Arria 10 10AX027 核心板的价值,正是在一块成熟核心板上集中提供 FPGA… · 2026/9/25 15:46:14
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37