news 2026/9/24 2:44:14

Mockery 全局配置指南:掌控不存在方法 Mock、未使用期望与内部类参数映射

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Mockery 全局配置指南:掌控不存在方法 Mock、未使用期望与内部类参数映射
  • 示例工程
  • 数据库
  • 教程
  • 后端

【免费下载链接】sql-server-samples

Azure 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
点击查看免费下载

本文基于 sql-server-samples 仓库中 Laravel 开发框架示例项目(samples/development-frameworks/laravel)所携带的 Mockery 测试框架文档,深入讲解 Mockery 的全局配置对象及其三大核心行为。读者将掌握通过\Mockery::getConfiguration()精细调控"是否允许 Mock 不存在的方法"、"是否允许未被满足的期望"以及"为 PHP 内部类方法手工声明参数映射"三项能力,并结合仓库源码与测试用例理解其底层生效机制,从而在 PHPUnit 测试套件中写出更严谨、更可维护的 Mock 测试。

Mockery 全局配置:一个单例,三项开关

Mockery 允许对部分核心行为进行"细粒度微调",其实现方式是一个单例配置对象(singleton configuration object)。在仓库携带的 Mockery 源码中,这个对象就是 library/Mockery/Configuration.php 中定义的Mockery\Configuration类,它持有当前配置的三项核心行为:

  1. 是否允许 Mock 实际并不存在的方法(默认开启);
  2. 是否允许存在从未被满足(即未被使用)的期望(默认开启);
  3. 为 PHP 内部类方法设置/读取参数映射——因为Reflection无法自动检测内部类方法的参数签名。

该单例由 library/Mockery.php 中的\Mockery::getConfiguration()静态方法惰性创建并返回:首次调用时实例化new \Mockery\Configuration()并缓存,此后所有调用共享同一配置实例。这意味着你在任意测试中修改配置,会立即影响整个测试进程内的所有 Mock 行为。

默认情况下,前两项行为都是启用的。不过文档明确指出,这种宽松默认值在某些场景下会带来"非预期后果":

  • 允许 Mock 不存在的方法,可能让基于真实类/对象建立的 Mock 与真实实现"脱节"——尤其当测试没有进行一定程度的集成测试(对对象装配关系进行验证)时,这类偏差很难被发现;
  • 允许未被满足的期望,意味着多余的 Mock 期望不会被察觉,从而污染测试代码,并可能误导阅读测试的人。

开关一:是否允许 Mock 不存在的方法

\Mockery::getConfiguration()->allowMockingNonExistentMethods(bool);

传入true允许该行为,传入false禁止该行为。两者都会立即生效,直到再次调用切换回来。一旦在禁止状态下检测到"Mock 了不存在的方法",Mockery 会在该处直接抛出Exception

源码中的默认值与存取逻辑

在 Configuration.php 中:

  • 受保护属性_allowMockingNonExistentMethod默认值为true
  • allowMockingNonExistentMethods($flag = true)将传入值强制转换为布尔并写入;
  • 读取端由mockingNonExistentMethodsAllowed()返回当前标记。

这一开关在哪里真正生效

从源码调用链看,该标记在三个关键位置被消费:

  1. Mock 初始化阶段:在 library/Mockery/Mock.php 的mockery_init()中,若禁止 Mock 不存在方法,Mock 对象会把自身通过mockery_getMethods()获取到的真实公共、非静态方法名收集进_mockery_mockableMethods数组,作为后续"可 Mock 方法白名单"。

  2. 魔术调用阶段:同样在 Mock.php 附近,调用不存在方法时,若禁止标志开启且方法既不存在于 partial 对象、也不可调用parent::$method,则该调用会被拦截。

  3. Demeter 链构建阶段:在 Mockery.php 的buildDemeterChain()中,当禁止 Mock 不存在方法、且当前 Mock 不是匿名 Mock、且链首方法不在mockery_getMockableMethods()列表中时,会抛出Mockery\Exception,错误信息明确提示:"Mockery's configuration currently forbids mocking the method ... as it does not exist on the class or object being mocked"。

测试用例佐证

仓库测试文件 tests/Mockery/MockTest.php 用多个用例验证了这一开关的行为:

  • testShouldIgnoreMissingDisallowMockingNonExistentMethodsUsingGlobalConfiguration:先allowMockingNonExistentMethods(false),再对真实类ClassWithMethods的 Mock 调用shouldReceive('nonExistentMethod'),预期抛出Mockery\Exception
  • testShouldIgnoreMissingCallingNonExistentMethodsUsingGlobalConfiguration:同样的禁止状态下直接调用$mock->nonExistentMethod(),预期抛出BadMethodCallException
  • testShouldIgnoreMissingCallingExistentMethods:禁止状态下调用真实存在的方法foo()bar()仍可正常返回,说明该开关只拦截"不存在的方法";
  • testMockWithNotAllowingMockingOfNonExistentMethodsCanBeGivenAdditionalMethodsToMockEvenIfTheyDontExistOnClass:即便全局禁止,仍可通过shouldAllowMockingMethod('testSomeNonExistentMethod')显式放行单个不存在的方法。

值得留意的是,testAnonymousMockWorksWithNotAllowingMockingOfNonExistentMethods表明匿名 Mock(不基于任何类)不受此限制——这正与buildDemeterChain()!$mock->mockery_isAnonymous()的判断逻辑相互印证:匿名 Mock 没有"真实类"可参照,自然谈不上"不存在的方法"。

开关二:是否允许未被满足的期望

\Mockery::getConfiguration()->allowMockingMethodsUnnecessarily(bool);

同样的语义:true允许,false禁止,立即生效。该行为针对的是"设置了期望(例如使用了zeroOrMoreTimes()这类零次或多次约束),却从未被调用"的冗余期望。默认true时这些冗余期望会被静默忽略;一旦禁止,检测到未使用的期望就会抛出Exception

在源码 Configuration.php 中,对应属性_allowMockingMethodsUnnecessarily默认值为true,写入端allowMockingMethodsUnnecessarily($flag = true)同样做了布尔强制转换,读取端为mockingMethodsUnnecessarilyAllowed()

启用这一约束后,测试代码中"写了却永远用不到的期望"会被立即揪出,促使开发者清理冗余断言,让测试意图更清晰、更贴近被测对象的真实交互。文档特别提醒:禁用这两项行为都应谨慎权衡,因为它们必然会削减去 Mockery 的一部分灵活性——例如过度依赖真实类方法存在性检查,会让以"宽松 Mock"著称的测试风格变得寸步难行。

能力三:内部类方法的参数映射(Reflection 的盲区补丁)

\Mockery::getConfiguration()->setInternalClassMethodParamMap($class, $method, array $paramMap); \Mockery::getConfiguration()->getInternalClassMethodParamMap($class, $method);

这两个方法用于为PHP 内部类(internal classes,例如 SPL 类,或 PECL 扩展类如 ext/mongo 的MongoCollection)的方法定义参数签名。原因是 PHP 的Reflection无法分析内部类方法的参数,Mockery 也就无法得知这些方法的参数信息。绝大多数情况下你根本不需要用到它;它主要出现在"内部类方法的某个参数是按引用传递(pass-by-reference)"的场景——此时必须确保参数签名中正确包含&符号,因为 Mockery 无法为内部类自动补上这个符号。

源码实现细节

在 Configuration.php 中:

  • setInternalClassMethodParamMap($class, $method, array $map)将映射写入_internalClassParamMap类名与方法名都会被strtolower()归一化后存储,因此调用时大小写不敏感;
  • getInternalClassMethodParamMap($class, $method)按同样的小写规则读取,未命中时返回null
  • 配套提供resetInternalClassMethodParamMaps()可清空全部已覆盖的参数映射,以及getInternalClassMethodParamMaps()返回整个映射数组。

映射如何流入 Mock 生成流程

参数映射的消费点在 library/Mockery/Container.php 的mock()方法中:创建MockConfigurationBuilder后,立即调用$builder->setParameterOverrides(\Mockery::getConfiguration()->getInternalClassMethodParamMaps()),将全局配置中的内部类参数映射注入构建器,从而影响后续 Mock 类的生成与方法的签名定义。

实战示例:MongoCollection::insert() 的按引用参数

仓库文档 pass_by_reference_behaviours.rst 给出了完整示例。MongoCollection是 PECL mongo 扩展提供的内部类,其insert()方法第一个参数是数据数组,第二个是可选选项数组;原始数据数组会被更新——即insert()含有一个按引用传递的参数,调用后数据中会新增_id字段。Mockery 无法用 Reflection 分析这一点,因此需要手工配置参数映射:

\Mockery::getConfiguration()->setInternalClassMethodParamMap( 'MongoCollection', 'insert', array('&$data', '$options = array()') ); $m = \Mockery::mock('MongoCollection'); $m->shouldReceive('insert')->with( \Mockery::on(function(&$data) { if (!is_array($data)) return false; $data['_id'] = 123; return true; }), \Mockery::any() ); $data = array('a'=>1,'b'=>2); $m->insert($data); $this->assertTrue(isset($data['_id'])); $this->assertEquals(123, $data['_id']); \Mockery::resetContainer();

注意三点实操要点:

  1. 参数签名数组中,'&$data'&必须显式写出,这是 Mockery 无法自动补充的关键信息;
  2. 通过\Mockery::on()闭包参数匹配器模拟"按引用修改":闭包接收&$data,在内部写入_id字段,使外部变量$data同步被修改;
  3. 测试结束后调用\Mockery::resetContainer()清理容器与配置状态,避免影响后续测试。

这段代码同时是仓库 Mockery.php 中parseShouldReturnArgsExpectation机制配合工作的典型范例:with()中多个参数依次用\Mockery::on()闭包和\Mockery::any()匹配器进行校验。

配置的作用范围、生命周期与 Laravel 实践建议

由于配置对象是全局单例且改动立即生效,在实际测试中需要特别注意以下几点:

  • 作用范围:所有三项配置均作用于整个测试进程,而非单个 Mock 或单个测试方法;
  • 生命周期:配置在进程内持续累积,若在某测试中关闭了某行为,需在测试结束后显式恢复(仓库测试如 MockTest.php 的模式即是allowMockingNonExistentMethods(false)用完后立即...->allowMockingNonExistentMethods(true)还原,或直接\Mockery::resetContainer());
  • 优先考虑局部约束:全局开关通常适合"整批测试套件"级别的统一约束(例如在setUp()或测试基类中统一关闭某行为);若只想约束单个 Mock,优先使用shouldAllowMockingMethod()shouldAllowMockingProtectedMethods()等局部 API,避免全局状态污染;
  • 按引用参数是硬性需求:凡是涉及内部类方法的按引用参数 Mock,setInternalClassMethodParamMap()不是可选项而是必选项,否则生成的 Mock 方法签名与真实方法不一致,行为将无法正确模拟。

在 Laravel 项目中,Mockery 通常经 PHPUnit 的测试监听器与Mockery\Adapter\Phpunit集成(见仓库 vendor 目录下 MockeryPHPUnitIntegration.php)。当你需要"严禁 Mock 幽灵方法"或"杜绝冗余期望"这类团队级测试纪律时,可将\Mockery::getConfiguration()的开关集中放置在测试基类的初始化逻辑中统一启用,再通过上述局部 API 按需放行,即可在保留 Mockery 灵活性的同时获得更严格的测试保障。

小结

Mockery 的全局配置对象通过三个精炼的 API 族,将"宽松"与"严谨"之间的尺度交还给了测试编写者:allowMockingNonExistentMethods()控制 Mock 与真实类之间的同步纪律,allowMockingMethodsUnnecessarily()控制测试代码中的冗余期望,setInternalClassMethodParamMap()/getInternalClassMethodParamMap()则弥补了 PHP Reflection 对内部类方法的分析盲区。理解它们各自在 Configuration.php、Mock.php、Container.php 与 Mockery.php 中的生效路径,以及 MockTest.php 中的行为验证,是写出既灵活又严谨的 PHP 单元测试的关键一步。

  • 示例工程
  • 数据库
  • 教程
  • 后端

【免费下载链接】sql-server-samples

Azure 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 2:44:07

【无人机控制】轴承式继电器无人机控制Matlab实现

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、算法改进、程序设计科研仿真。🍎 往期回顾关注个人主页:完整代码获取 定制创新 论文复现私信🍊个人信条:做科研&#xff0c…

作者头像 李华
网站建设 2026/9/24 2:42:35

NXP NFC天线设计工具实战:FR4与Flex天线匹配仿真与打样指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 2:42:18

EMQX 监听器连接速率限制配置与热更新即时生效机制解析

后端物联网消息队列通信 【免费下载链接】emqx The most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles 项目地址: https://gitcode.com/gh_mirrors/em/emqx 点击查看 免费下载 导读 本文围绕 EMQX 的变更记录 fix-15783 展开&…

作者头像 李华
网站建设 2026/9/24 2:41:02

自顶向下习题答案怎么用?把PDF变成你的第二个网络老师

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 2:40:47

自适应啸叫抑制算法原理与MATLAB实现:从反馈抵消到陷波器组

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华