参数验证
在设置期望时,传递给 with() 声明的参数决定了匹配方法调用的标准。因此,我们可以为一个方法设置多个期望,每个期望都通过预期的参数来区分。这种参数匹配是基于 “最佳匹配” 原则进行的。这确保了显式匹配优先于泛化匹配。
显式匹配仅指预期参数和实际参数易于相等的情况(即使用 === 或 ==)。使用正则表达式、类类型提示和可用的通用匹配器可以实现更泛化的匹配。通用匹配器的目的是允许以非显式方式定义参数,例如,传递给 with() 的 Mockery::any() 将匹配该位置的 any 参数。
Mockery 的通用匹配器并未涵盖所有可能性,但提供了对 Hamcrest 匹配器库的可选支持。Hamcrest 是一个同名 Java 库的 PHP 移植版本(该库也已移植到 Python、Erlang 等)。通过使用 Hamcrest,Mockery 无需重复 Hamcrest 已经令人印象深刻的实用功能,这本身也促进了自然英语 DSL 的使用。
下面的例子展示了 Mockery 匹配器及其 Hamcrest 等效项(如果存在)。Hamcrest 使用函数(没有命名空间)。
\Hamcrest\Matchers 类以静态方法的形式公开。因此,identicalTo($arg) 与 \Hamcrest\Matchers::identicalTo($arg) 相同。最常见的匹配器是 with() 匹配器:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(1);
它告诉 Mockery,它应该接收一个对 foo 方法的调用,其中整数 1 作为参数。在这种情况下,Mockery 首先尝试使用 ===(全等)比较运算符来匹配参数。如果参数是基本类型,并且全等比较失败,Mockery 会回退到 ==(相等)比较运算符。
当将对象作为参数进行匹配时,Mockery 只进行严格的 === 比较,这意味着只有 同一个 $object 才会匹配:
$object = new stdClass();
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with($object);
// Hamcrest equivalent
$mock->shouldReceive("foo")
->with(identicalTo($object));
stdClass 的不同实例将 不 匹配。
Mockery\Matcher\MustBe 匹配器已被弃用。如果我们需要对对象进行宽松比较,可以使用 Hamcrest 的 equalTo 匹配器:
$mock->shouldReceive("foo")
->with(equalTo(new stdClass));
在不关心参数类型或值的情况下,只要有任何参数存在,我们使用 any():
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::any());
// Hamcrest equivalent
$mock->shouldReceive("foo")
->with(anything())
传递到此参数槽位的任何和所有内容都将不受约束地通过。
验证类型和资源
type() 匹配器接受任何可以附加到 is_ 以形成有效类型检查的字符串。
要匹配任何 PHP 资源,我们可以这样做:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::type('resource'));
// Hamcrest equivalents
$mock->shouldReceive("foo")
->with(resourceValue());
$mock->shouldReceive("foo")
->with(typeOf('resource'));
如果提供给方法的参数是 PHP 资源,它将从 is_resource() 调用返回 true。例如,\Mockery::type('float') 或 Hamcrest 的 floatValue() 和 typeOf('float') 检查使用 is_float(),而 \Mockery::type('callable') 或 Hamcrest 的 callable() 使用 is_callable()。
type() 匹配器还接受一个类或接口名,用于对实际参数进行 instanceof 评估。Hamcrest 使用 anInstanceOf()。
完整的类型检查器列表可在 php.net 上找到,或在 Hamcrest 代码 中浏览 Hamcrest 的函数列表。
复杂参数验证
如果我们想执行复杂的参数验证,on() 匹配器是无价的。它接受一个闭包(匿名函数),实际参数将被传递给该闭包。
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::on(closure));
如果闭包的求值(即返回)为布尔值 true,则假定参数匹配期望。
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::on(function ($argument) {
if ($argument % 2 == 0) {
return true;
}
return false;
}));
$mock->foo(4); // 匹配期望
$mock->foo(3); // 抛出 NoMatchingExpectationException
on() 匹配器。我们还可以通过向 withArgs() 方法传递一个闭包来执行参数验证。闭包将接收传递给预期方法调用的所有参数,如果它的求值(即返回)为布尔值 true,则假定参数列表匹配期望:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->withArgs(closure);
闭包也可以处理可选参数,因此如果预期方法调用中缺少可选参数,并不一定意味着参数列表不匹配期望。
$closure = function ($odd, $even, $sum = null) {
$result = ($odd % 2 != 0) && ($even % 2 == 0);
if (!is_null($sum)) {
return $result && ($odd + $even == $sum);
}
return $result;
};
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->withArgs($closure);
$mock->foo(1, 2); // 匹配期望:不需要可选参数
$mock->foo(1, 2, 3); // 也匹配期望:可选参数通过验证
$mock->foo(1, 2, 4); // 不匹配期望:可选参数未通过验证
with() 会尝试对参数进行模式匹配,尝试将参数用作正则表达式。随着时间的推移,这被证明不是一个好主意,因此我们删除了此功能,并引入了 Mockery::pattern()。如果我们想根据正则表达式匹配参数,我们可以使用 \Mockery::pattern():
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::pattern('/^foo/'));
// Hamcrest equivalent
$mock->shouldReceive('foo')
->with(matchesPattern('/^foo/'));
ducktype() 匹配器是按类类型匹配的替代方法:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::ducktype('foo', 'bar'));
它匹配任何包含所提供的可调用方法列表的对象参数。
ducktype() 匹配器。捕获参数
如果我们要对单个参数执行多次验证,capture 匹配器提供了一种流线化的替代方案,而不是使用 on() 匹配器。它接受一个变量,实际参数将被赋给该变量。
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::capture($bar));
这会将传递给 foo 的 任何 参数赋值给本地 $bar 变量,然后使用断言执行额外的验证。
capture 匹配器总是求值为 true。因此,我们应该始终执行额外的参数验证。附加参数匹配器
not() 匹配器匹配与匹配器的参数不相等或不全等的任何参数:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::not(2));
// Hamcrest equivalent
$mock->shouldReceive('foo')
->with(not(2));
anyOf() 匹配任何等于给定参数中 任意一个 的参数:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::anyOf(1, 2));
// Hamcrest equivalent
$mock->shouldReceive('foo')
->with(anyOf(1,2));
notAnyOf() 匹配任何与给定参数中 任意一个 不相等或不全等的参数:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::notAnyOf(1, 2));
notAnyOf() 匹配器。subset() 匹配任何包含给定数组子集的数组参数:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::subset(array(0 => 'foo')));
这强制了键名和值都匹配,即每个实际元素的键和值都会被比较。
hasEntry() 或 hasKeyValuePair() 检查单个条目。contains() 匹配任何包含所列值的数组参数:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::contains(value1, value2));
键的命名被忽略。
hasKey() 匹配任何包含给定键名的数组参数:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::hasKey(key));
hasValue() 匹配任何包含给定值的数组参数:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::hasValue(value));