A common need for mock objects is the ability to variable multiple invocations on that object. Phake allows you to use Phake::verify() multiple times on the same object. A notable difference between Phake and PHPUnit’s mocking framework is the ability to mock multiple invocations of the same method with no regard for call sequences. The PHPUnit mocking test below would fail for this reason.
Example 3.1. Multiple Invocations With PHPUnit Mocks - Bad Example
<?php class MyTest extends PHPUnit_Framework_TestCase { public function testPHPUnitMock() { $mock = $this->getMock('PhakeTest_MockedClass'); $mock->expects($this->once())->method('fooWithArgument') ->with('foo'); $mock->expects($this->once())->method('fooWithArgument') ->with('bar'); $mock->fooWithArgument('foo'); $mock->fooWithArgument('bar'); } } ?>
The reason this test fails is because by default PHPUnit only allows a singl expectation per method. The way you can fix this is by using the at() matcher. This allows you to specify the index of the invocation you want to match again. So to make the test above work you would have to change it.
Example 3.2. Multiple Invocations With PHPUnit Mocks - Good Example
<?php class MyTest extends PHPUnit_Framework_TestCase { public function testPHPUnitMock() { $mock = $this->getMock('PhakeTest_MockedClass'); //NOTICE this is now at() instead of once() $mock->expects($this->at(0))->method('fooWithArgument') ->with('foo'); //NOTICE this is now at() instead of once() $mock->expects($this->at(1))->method('fooWithArgument') ->with('bar'); $mock->fooWithArgument('foo'); $mock->fooWithArgument('bar'); } } ?>
This test will now run as expected. There is still one small problem however and that is that you are now testing not just the invocations but also the order of invocations. Many times the order in which two calls are made really do not matter. If swapping the order of two method calls will not break your application then there is no reason to enforce that code structure through a unit test. Unfortunately, you cannot have multiple invocations of a method in PHPUnit without enforcing call order. In Phake these two notions of call order and multiple invocations are kept completely distinct. Here is the same test written using Phake.
Example 3.3. Multiple Invocations With Phake
<?php class MyTest extends PHPUnit_Framework_TestCase { public function testPHPUnitMock() { $mock = Phake::mock('PhakeTest_MockedClass'); $mock->fooWithArgument('foo'); $mock->fooWithArgument('bar'); Phake::verify($mock)->fooWithArgument('foo'); Phake::verify($mock)->fooWithArgument('bar'); } } ?>
You can switch the calls around in this example as much as you like and the test will still pass. You can mock as many different invocations of the same method as you need.
If you would like to verify the exact same parameters are used on a method multiple times (or they all match the same constraints multiple times) then you can use the verification mode parameter of Phake::verify(). The second parameter to Phake::verify() allows you to specify how many times you expect that method to be called with matching parameters. If no value is specified then the default of one is used. The other options are:
- Phake::times($n) – Where $n equals the exact number of times you expect the method to be called.
- Phake::atLeast($n) – Where $n is the minimum number of times you expect the method to be called.
- Phake::atMost($n) – Where $n is the most number of times you would expect the method to be called.
Here is an example of this in action.
Example 3.4. Multiple Invocations of the Same Call With Phake
<?php class MyTest extends PHPUnit_Framework_TestCase { public function testPHPUnitMock() { $mock = Phake::mock('PhakeTest_MockedClass'); $mock->fooWithArgument('foo'); $mock->fooWithArgument('foo'); Phake::verify($mock, Phake::times(2))->fooWithArgument('foo'); } } ?>