期望声明
Mockery::close(),最好在像 tearDown 或 _after 这样的回调方法中调用(取决于我们是否将 Mockery 与其他框架集成)。这个静态调用会清理当前测试使用的 Mockery 容器,并执行任何用于验证期望的任务。一旦我们创建了一个模拟对象,我们通常希望开始定义它的具体行为(以及它应如何被调用)。这时,Mockery 的期望声明就派上用场了。
声明方法调用期望
要告诉我们的测试替身期望调用某个方法,我们使用 shouldReceive 方法:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method');
这是所有其他期望和约束附加的起点。
我们可以声明多个方法调用期望:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method_1', 'name_of_method_2');
所有这些都会继承任何链式期望或约束。
我们可以在声明方法调用期望的同时,指定它们的返回值:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive([
'name_of_method_1' => 'return value 1',
'name_of_method_2' => 'return value 2',
]);
也有一种简写方式设置方法调用期望及其返回值:
$mock = \Mockery::mock('MyClass', ['name_of_method_1' => 'return value 1', 'name_of_method_2' => 'return value 2']);
所有这些都可以附加额外的链式期望或约束。
我们可以声明测试替身 不应期望调用 给定方法名:
$mock = \Mockery::mock('MyClass');
$mock->shouldNotReceive('name_of_method');
此方法是 shouldReceive()->never() 的便捷写法。
声明方法参数期望
对于我们声明期望的方法,可以添加约束,使得期望只适用于使用指定参数调用的方法:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->with($arg1, $arg2, ...);
// 或者
$mock->shouldReceive('name_of_method')
->withArgs([$arg1, $arg2, ...]);
可以使用内置匹配器类提供更灵活的参数匹配(后文将讲)。例如,\Mockery::any() 匹配 with() 参数列表中该位置传入的任意值。Mockery 也支持 Hamcrest 库匹配器,例如 Hamcrest 的 anything() 等同于 \Mockery::any()。
注意:这意味着所有附加的期望仅适用于使用这些 精确参数 调用的方法:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->with('Hello');
$mock->foo('Goodbye'); // 抛出 NoMatchingExpectationException
这允许根据提供给方法的参数设置不同的期望。
使用闭包进行参数匹配
我们可以使用闭包匹配所有传入参数,而不是为每个参数提供内置匹配器:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withArgs(closure);
提供的闭包接收所有传递给预期方法调用的参数。通过这种方式,只有当传递的参数使闭包的求值为真时,此预期才适用于该方法调用。
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->withArgs(function ($arg) {
if ($arg % 2 == 0) {
return true;
}
return false;
});
$mock->foo(4); // matches the expectation
$mock->foo(3); // throws a NoMatchingExpectationException
部分参数匹配
我们可以提供部分参数,只要模拟方法调用中包含这些值即可:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withSomeOfArgs(arg1, arg2, arg3, ...);
预期的参数顺序不重要。检查预期的值是否包含在内,但类型必须匹配。
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->withSomeOfArgs(1, 2);
$mock->foo(1, 2, 3); // matches the expectation
$mock->foo(3, 2, 1); // matches the expectation (passed order doesn't matter)
$mock->foo('1', '2'); // throws a NoMatchingExpectationException (type should be matched)
$mock->foo(3); // throws a NoMatchingExpectationException
任意或无参数
我们可以声明期望匹配任意参数调用:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withAnyArgs();
默认情况下就是 withAnyArgs(),除非另行指定。
也可以声明期望只匹配零参数调用:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withNoArgs();
声明期望返回值
andReturnNull()或andReturn([null])表示返回null。andReturnUsing(closure, ...)可根据方法调用的参数计算返回值。andReturnArg(index)返回调用时指定索引的参数。andReturnSelf()用于流接口,返回模拟对象自身。
可以使用 andReturn() 指定模拟方法的返回值:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturn($value);
这为预期的函数调用设置了返回值。
可以为多个返回值设置预期。通过提供一系列返回值,我们告诉 Mockery 在每次对该方法进行后续调用时应返回什么值。
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturn($value1, $value2, ...)
第一次调用将返回 $value1,第二次调用将返回 $value2。
如果我们调用的次数多于我们声明的返回值数量,Mockery 将为任何后续的方法调用返回最后一个值。
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->andReturn(1, 2, 3);
$mock->foo(); // int(1)
$mock->foo(); // int(2)
$mock->foo(); // int(3)
$mock->foo(); // int(3)
同样的,使用另一种语法也可以实现:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnValues([$value1, $value2, ...])
它接受一个简单的数组,而不是参数列表。返回值的顺序由给定数组的数字索引决定,一旦先前的返回值被用尽,最后一个数组元素将在所有后续调用中被返回。
以下两个选项主要用于与测试读者交流:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnNull();
// or
$mock->shouldReceive('name_of_method')
->andReturn([null]);
它们将模拟对象的方法调用标记为返回 null 或无值。
有时我们想根据传递给方法的参数来计算方法调用的返回结果。我们可以使用 andReturnUsing() 方法来实现这一点,该方法接受一个或多个闭包:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnUsing(closure, ...);
闭包可以通过像 andReturn() 那样作为额外参数传递来排队。
有时,回显方法调用时的某个参数会很有用。在这种情况下,我们可以使用 andReturnArg() 方法;要返回的参数由它在参数列表中的索引指定:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnArg(1);
这将返回方法调用时参数列表中的第二个参数(索引 #1)。
andReturnUsing() 或 andReturnArg() 与 andReturn() 混合使用。如果我们在模拟流式接口,以下方法将会很有帮助:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnSelf();
它将返回值设置为被模拟的类名。
抛出异常
我们可以让模拟对象的方法抛出异常:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andThrow(new Exception);
它在被调用时会抛出给定的 Exception 对象。
除了对象,我们还可以传入 Exception 类、消息和/或代码,以便在从模拟方法抛出 Exception 时使用:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andThrow('exception_name', 'message', 123456789);
设置公共属性
结合一个预期来使用,这样当一个匹配的方法被调用时,我们可以使用 andSet() 或 set() 方法,将模拟对象的公共属性设置为一个指定的值。
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andSet($property, $value);
// or
$mock->shouldReceive('name_of_method')
->set($property, $value);
当我们需要调用被模拟类的真实方法并返回其结果时,passthru() 方法会告诉期望绕过返回队列。
$mock->shouldReceive('name_of_method')
->passthru();
它允许对真实方法应用期望匹配和调用计数验证,同时仍使用预期的参数调用真实的类方法。
passthru() 的作用是:当 name_of_method 被调用时,不要返回预设的值,而是直接执行这个方法在原始对象中的真实逻辑。声明调用次数期望
除了对方法调用的参数和返回值设置期望外,我们还可以设置方法应被调用多少次的期望。
当调用次数的期望没有满足时,会抛出 \Mockery\Expectation\InvalidCountException 异常。
\Mockery::close(),例如在 PHPUnit 的 tearDown() 方法中。否则 Mockery 将不会验证对我们的模拟对象进行的调用。我们可以声明预期的方法可能被 调用零次或多次:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->zeroOrMoreTimes();
- 精确次数:
这是所有方法的默认设置,除非另有设置。
要告诉 Mockery 期望某个方法被调用的确切次数,我们可以使用以下方法:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->times($n);
其中 $n 是该方法应被调用的次数。
- 常用快捷方法:
一些最常见的用例有它们的简写方法。
要声明预期的方法必须只被调用一次:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')->once(); // 调用一次
$mock->shouldReceive('name_of_method')->twice(); // 调用两次
$mock->shouldReceive('name_of_method')->never(); // 永不调用
调用次数修饰器
调用次数的期望可以设置修饰符。
- 最少调用次数:
如果我们要告诉 Mockery 一个方法应该被调用的最少次数,我们使用 atLeast():
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->atLeast()
->times(3);
atLeast()->times(3) 意味着(在匹配方法参数的情况下)该调用必须至少被调用三次,但绝不能少于三次。
- 最多调用次数:
同样,我们可以使用 atMost() 告诉 Mockery 一个方法应该被调用的最大次数:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->atMost()
->times(3);
atMost()->times(3) 意味着该调用被调用的次数不能超过三次。如果该方法完全没有被调用,期望仍然会得到满足。
- 范围调用次数:
我们也可以使用 between() 设置一个调用次数范围:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->between($min, $max);
这实际上与使用 atLeast()->times($min)->atMost()->times($max) 完全相同,但作为简写形式提供。它后面可以跟一个没有参数的 times() 调用,以保持 API 的自然语言可读性。
多次调用不同期望
如果一个方法预期被多次调用,并且带有不同的参数和/或返回值,我们可以简单地重复设置期望。当然,如果我们期望对不同方法进行多次调用,这也同样适用。
$mock = \Mockery::mock('MyClass');
// Expectations for the 1st call
$mock->shouldReceive('name_of_method')
->once()
->with('arg1')
->andReturn($value1)
// 2nd call to same method
->shouldReceive('name_of_method')
->once()
->with('arg2')
->andReturn($value2)
// final call to another method
->shouldReceive('other_method')
->once()
->with('other')
->andReturn($value_other);
期望声明工具
ordered():声明方法调用顺序。
声明此方法预期以相对于类似标记方法的特定顺序被调用。ordered(group):将方法分组,组内可任意顺序,但与组外顺序有关。
此处的顺序由设置模拟时实际使用此修饰符的顺序决定。
声明该方法属于一个有序组(可以命名或编号)。组内的方法可以以任何顺序调用,但组外有序的调用与该组相关联:globally():声明跨多个模拟对象的调用顺序。
我们可以进行设置,让method1在group1之前被调用,而group1又在method2之前被调用。
当在ordered()或ordered(group)之前调用时,它声明此顺序应用于所有模拟对象(而不仅仅是当前模拟对象):byDefault():标记默认期望,可被后续非默认期望覆盖。
这允许在多个模拟对象之间指定顺序期望。byDefault()将一个期望标记为默认。除非创建了非默认期望,否则默认期望将被应用:getMock():返回当前模拟对象,方便链式操作:
这些后来的期望会立即取代之前定义的默认期望。这很有用,这样我们就可以在单元测试的setup()方法中设置默认模拟,然后在需要时在特定测试中调整它们。
从期望链中返回当前模拟对象:
在某些情况下很有用,例如当我们希望将模拟设置保留为单个语句时:
$mock = \Mockery::mock('foo')->shouldReceive('foo')->andReturn(1)->getMock();