Lzh on GitHub

参数验证

在设置期望时,传递给 with() 声明的参数决定了匹配方法调用的标准。因此,我们可以为一个方法设置多个期望,每个期望都通过预期的参数来区分。这种参数匹配是基于 “最佳匹配” 原则进行的。这确保了显式匹配优先于泛化匹配。

在设置期望时,传递给 with() 声明的参数决定了匹配方法调用的标准。因此,我们可以为一个方法设置多个期望,每个期望都通过预期的参数来区分。这种参数匹配是基于 “最佳匹配” 原则进行的。这确保了显式匹配优先于泛化匹配。

显式匹配仅指预期参数和实际参数易于相等的情况(即使用 =====)。使用正则表达式、类类型提示和可用的通用匹配器可以实现更泛化的匹配。通用匹配器的目的是允许以非显式方式定义参数,例如,传递给 with()Mockery::any() 将匹配该位置的 any 参数。

Mockery 的通用匹配器并未涵盖所有可能性,但提供了对 Hamcrest 匹配器库的可选支持。Hamcrest 是一个同名 Java 库的 PHP 移植版本(该库也已移植到 Python、Erlang 等)。通过使用 Hamcrest,Mockery 无需重复 Hamcrest 已经令人印象深刻的实用功能,这本身也促进了自然英语 DSL 的使用。

下面的例子展示了 Mockery 匹配器及其 Hamcrest 等效项(如果存在)。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
没有 Hamcrest 版本的 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); // 不匹配期望:可选参数未通过验证
在以前的版本中,Mockery 的 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'));

它匹配任何包含所提供的可调用方法列表的对象参数。

没有 Hamcrest 版本的 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));
没有 Hamcrest 版本的 notAnyOf() 匹配器。

subset() 匹配任何包含给定数组子集的数组参数:

$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
    ->with(\Mockery::subset(array(0 => 'foo')));

这强制了键名和值都匹配,即每个实际元素的键和值都会被比较。

没有 Hamcrest 版本的此功能,但 Hamcrest 可以使用 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));