Mockery 参数验证(Argument Validation)指南:掌握 with() 匹配器与 Hamcrest 对照用法
2026/9/23 23:39:52 网站建设 项目流程
  • 示例工程
  • 数据库
  • 教程
  • 后端

【免费下载链接】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 0.9 框架文档为蓝本,系统讲解 Mockery 的**参数验证(Argument Validation)**机制——即通过with()声明为期望(expectation)设置参数匹配规则,从而区分同一方法上的多条期望。读完本文,你将掌握 Mockery 内置的全部通用匹配器(anytypeonducktypemustBenotanyOfnotAnyOfsubsetcontainshasKeyhasValue)的语义与用法,理解与 Hamcrest 库的对应关系,并看到这些匹配器在仓库内源码中的真实实现,为在 PHPUnit 单测中编写精确、可维护的 Mock 期望打下基础。

说明:Mockery 由 composer.json 中"mockery/mockery": "0.9.*"引入(见 samples/development-frameworks/laravel/composer.json),其文档位于vendor/mockery/mockery/docs/reference/,本文即是对其中 argument_validation.rst 的展开解读。

参数匹配的基本原理:期望如何与调用对应

在 Mockery 中,当你设置一个期望时,传给with()声明的参数决定了该期望与真实方法调用之间的匹配标准。因此,你可以为同一个方法设置多条期望,每条期望通过参数不同而相互区分。参数匹配采用"best fit"(最契合优先)原则:显式匹配优先于泛化匹配。

所谓"显式匹配",是指期望参数与实际参数可以被直接比较(即通过=====完成判定);而更泛化的匹配则借助正则表达式、类型提示(class hinting)以及 Mockery 提供的通用匹配器实现。泛化匹配器的意义在于,允许你用非显式的方式描述参数,例如在with()中传入\Mockery::any(),表示该位置接受任意参数。

从源码结构看,这一设计体现在匹配器的抽象基类中。所有内置匹配器都继承自 library/Mockery/Matcher/MatcherAbstract.php,其中:

  • 构造函数接收"期望值"_expected
  • 抽象方法match(&$actual)负责判断实际参数是否命中——注意实际参数以引用方式传入,以保留指向原始方法参数的引用链;
  • 抽象方法__toString()返回匹配器的字符串表示,便于在失败消息中呈现期望内容。

例如Any匹配器的match()直接return true(见 library/Mockery/Matcher/Any.php),这正对应文档中"任何参数都能通过"的描述。

匹配器的入口:Mockery 静态工厂方法

Mockery 通过 library/Mockery.php 提供一组静态工厂方法来创建上述匹配器实例,与文档一一对应:

文档中的写法工厂方法(源码位置)返回的匹配器类
\Mockery::any()any()Matcher\Any
\Mockery::type($expected)type()Matcher\Type
\Mockery::ducktype(...)ducktype()Matcher\Ducktype
\Mockery::subset(array $part)subset()Matcher\Subset
\Mockery::contains(...)contains()Matcher\Contains
\Mockery::hasKey($key)hasKey()Matcher\HasKey
\Mockery::hasValue($val)hasValue()Matcher\HasValue
\Mockery::on($closure)on()Matcher\Closure
\Mockery::mustBe($expected)mustBe()Matcher\MustBe
\Mockery::not($expected)not()Matcher\Not
\Mockery::anyOf(...)anyOf()Matcher\AnyOf
\Mockery::notAnyOf(...)notAnyOf()Matcher\NotAnyOf

注意ducktypecontainsanyOfnotAnyOf使用func_get_args()收集可变数量的参数,因此可以一次传入多个方法名或值。

内置匹配器逐一详解

以下逐一展开文档中的每个匹配器示例,并给出对应的 Hamcrest 等价写法(Hamcrest 使用无命名空间的函数,Mockery 支持可选接入 Hamcrest 匹配器库以扩展能力;Mockery 官方强烈推荐使用 Hamcrest,因为 Mockery 无需重复实现 Hamcrest 已有的丰富工具,后者还提供贴近自然英语的 DSL)。

1. 显式值匹配:with(1)

with(1)

匹配整数1。它通过===(恒等)测试;同时,Mockery 也支持较宽松的==(相等)检查,此时字符串'1'也能匹配该参数位。这是文档所述的"显式匹配"的基础形态。

2. 匹配任意参数:with(\Mockery::any())

with(\Mockery::any()) // Mockery 写法 with(anything()) // Hamcrest 等价写法

匹配任意参数。传在该参数位上的任何值都不受约束地通过,实现上即Matcher\Any::match()恒返回true

3. 类型匹配:with(\Mockery::type('resource'))

with(\Mockery::type('resource')) // Mockery 写法 with(resourceValue()) // Hamcrest 等价写法 with(typeOf('resource')) // Hamcrest 的另一种等价写法

匹配任何 resource 类型的值,即is_resource()返回true。Type 匹配器接受任何可以拼接为is_前缀形成合法类型检查的字符串:

  • \Mockery::type('float')或 Hamcrest 的floatValue()typeOf('float')使用is_float()检查;
  • \Mockery::type('callable')或 Hamcrest 的callable()使用is_callable()检查。

Type 匹配器还接受类名或接口名,此时会对实际参数执行instanceof求值(Hamcrest 对应anInstanceOf())。其底层实现可在 library/Mockery/Matcher/Type.php 中看到:

public function match(&$actual) { $function = 'is_' . strtolower($this->_expected); if (function_exists($function)) { return $function($actual); } elseif (is_string($this->_expected) && (class_exists($this->_expected) || interface_exists($this->_expected))) { return $actual instanceof $this->_expected; } return false; }

即:先尝试把期望值小写化为is_xxx()函数;若不存在该函数,再尝试作为类/接口名进行instanceof判断。完整的 PHP 类型检查函数清单可查阅 PHP 手册的 Variable handling 章节,Hamcrest 函数列表可浏览其 Hamcrest.php 源码。

4. 回调式匹配:with(\Mockery::on(closure))

with(\Mockery::on(closure))

On 匹配器接受一个闭包(匿名函数),实际参数会被传入该闭包;如果闭包返回布尔值true,则认为该参数命中期望。当你的参数期望逻辑过于复杂、或现有默认匹配器无法表达时,这个匹配器极为有用。Hamcrest 没有对应实现。

5. 正则匹配:with('/^foo/')

with('/^foo/') // Mockery 写法 with(matchesPattern('/^foo/')) // Hamcrest 等价写法

参数声明器会假定任何给定的字符串都可能是一条正则表达式,用于对实际参数做匹配。正则选项仅在满足以下两个条件时才启用

  1. 不存在=====的显式匹配;
  2. 该正则被验证为合法正则(即preg_match()不会返回false)。

如果你不喜欢这种隐式正则探测的行为,Hamcrest 提供了更显式的matchesPattern()函数。

6. 鸭子类型匹配:with(\Mockery::ducktype('foo', 'bar'))

with(\Mockery::ducktype('foo', 'bar'))

Ducktype 匹配器是"按类类型匹配"之外的另一种思路:它匹配任何包含给定方法列表的对象,即只要对象实现了列出的这些方法就命中,而不管其具体类是什么("走起来像鸭子、叫起来像鸭子,那就是鸭子")。Hamcrest 没有对应实现。

7. 严格恒等匹配:with(\Mockery::mustBe(2))

with(\Mockery::mustBe(2)) // Mockery 写法 with(identicalTo(2)) // Hamcrest 等价写法

MustBe 匹配器比默认参数匹配器更严格。默认匹配器允许 PHP 的类型转换(casting),而 MustBe 还要求参数与期望值类型一致。例如默认情况下字符串'2'可以匹配整数 2(经==比较),但使用 MustBe 时该场景会失败,因为期望值是字符串而实际传入的是整数。

注意事项:对于对象,MustBe 匹配器不进行恒等(identical)比较,因为 PHP 中两个对象若不是完全相同的实例,比较必然失败——当对象在返回之前才被创建时,恒等匹配永远不可能成功,这反而成为一种阻碍。因此对象场景请改用其他匹配方式。

8. 取反匹配:with(\Mockery::not(2))

with(\Mockery::not(2)) // Mockery 写法 with(not(2)) // Hamcrest 等价写法

Not 匹配器匹配任何不等于且不恒等于其参数的值,即与参数既不==也不===才通过。

9. 多选一匹配:with(\Mockery::anyOf(1, 2))

with(\Mockery::anyOf(1, 2)) // Mockery 写法 with(anyOf(1, 2)) // Hamcrest 等价写法

匹配等于给定参数中任意一个的参数,即与列表中的任一值相等即命中。

10. 全排除匹配:with(\Mockery::notAnyOf(1, 2))

with(\Mockery::notAnyOf(1, 2))

匹配不等于且不恒等于给定参数中任何一个的参数。Hamcrest 没有对应实现。

11. 数组子集匹配:with(\Mockery::subset(array(0 => 'foo')))

with(\Mockery::subset(array(0 => 'foo')))

匹配任何包含给定数组子集的数组参数。它同时强制键名与键值的比对——即实际元素的键和值都要与子集中的键值对一一对应。Hamcrest 没有完全对应的实现,不过它可以用hasEntry()hasKeyValuePair()检查单个条目。

12. 数组包含值匹配:with(\Mockery::contains(value1, value2))

with(\Mockery::contains(value1, value2))

匹配任何包含所列值的数组参数,键名被忽略,只关心值是否存在。

13. 数组包含键匹配:with(\Mockery::hasKey(key))

with(\Mockery::hasKey(key))

匹配任何包含给定键名的数组参数。

14. 数组包含值匹配:with(\Mockery::hasValue(value))

with(\Mockery::hasValue(value))

匹配任何包含给定值的数组参数。

在 Laravel 项目中的落地:从依赖到测试

在 sql-server-samples 仓库的 Laravel 示例项目 samples/development-frameworks/laravel 中,Mockery 0.9 是require-dev依赖之一(见 composer.json),与 PHPUnit~4.0、PHPSpec~2.1一同服务于开发与测试阶段。该项目的运行环境为 PHP 7、Laravel 5.1、SQL Server PHP/ODBC 驱动(见 README.md),其tests/TestCase.php被注册到autoload-dev的 classmap 中。

将本文的匹配器应用到该项目的测试中时,典型模式如下:

$repository = Mockery::mock(TodoRepository::class); $repository->shouldReceive('find') ->with(\Mockery::type('int')) // 仅匹配整数 id ->once() ->andReturn($todo); $repository->shouldReceive('update') ->with(\Mockery::anyOf(1, 2), \Mockery::subset(['status' => 'done'])) ->andReturn(true);
  • 第一条期望利用type('int')限定find只接受整数主键;
  • 第二条期望用anyOf(1, 2)限定可操作的 id,用subset校验传入的更新数组必须包含status => done键值对。

这种写法正是文档所述"best fit"原则的实践:同一方法的多条with()期望共存时,Mockery 会优先匹配最显式、最具体的声明,保证测试行为可预期。

总结与选型建议

Mockery 的通用匹配器并不覆盖所有可能性,但它提供了可选的 Hamcrest 匹配器库支持作为补充。选型时可遵循以下原则:

  • 简单值与正则:优先使用默认显式匹配与正则探测,代码最简洁;
  • 类型、类与接口约束:使用type()(或 HamcrestanInstanceOf()/typeOf());
  • 复杂判定逻辑:使用on()闭包,逻辑自由度最高;
  • 数组结构断言:按需选择subsetcontainshasKeyhasValue,注意subset同时校验键名与键值,contains忽略键名;
  • 否定与多选notanyOfnotAnyOf可组合出取反与枚举语义;
  • 严格类型:需要同时约束值与类型时使用mustBe(),但切记对象不适合恒等比较。

需要进一步深入时,可直接查阅仓库内 Mockery 的完整文档目录 vendor/mockery/mockery/docs,以及匹配器实现源码 library/Mockery/Matcher 和工厂方法定义 library/Mockery.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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询