Using mock objects (Simple)
When writing unit tests you should always strive to isolate the code that you are testing as much as possible. This can be difficult at times. It is very common for methods in a class to interact with other classes. This interaction means that executing the method you are testing will result in you not only testing that method but you will also, in a sense, be testing all of the methods on external objects that this method calls. Ideally, you would only want to test the interaction with these methods. You would not want the testing to reach into the external method itself. To help solve this problem the concept of a mock object was created.
Mock objects are lightweight implementations or extensions of interfaces and objects that implement the public interface in a controlled way. When creating a mock object you can specify that any of that object's public or protected methods return a specific value. You can also set expectations as to how a method of that object will be called. This allows you to keep your tests focused on the specific class or method you want to test.
PHPUnit has an entire mocking library built directly into the framework. In recent years, some alternative mocking libraries such as Phake and Mockery have also been created.
How to do it...
Create the following test case in test/PlayerTest.php
:
<?php class PlayerTest extends PHPUnit_Framework_TestCase { private $player; private $hand; public function setUp() { $this->hand = $this->getMock('CardCollection'); $this->player = new Player('John Smith', $this->hand); } public function testDrawCard() { $deck = $this->getMock('CardCollection'); $deck->expects($this->once()) ->method('moveTopCardTo') ->with($this->identicalTo($this->hand)); $this->player->drawCard($deck); } public function testTakeCardFromPlayer() { $otherHand = $this->getMock('CardCollection'); $otherPlayer = $this->getMock('Player', array(), array('Jane Smith', $otherHand)); $card = $this->getMock('Card', array(), array('A', 'Spades')); $otherPlayer->expects($this->once()) ->method('getCard') ->with($this->equalTo(4)) ->will($this->returnValue($card)); $otherPlayer->expects($this->once()) ->method('getHand') ->will($this->returnValue($otherHand)); $this->hand->expects($this->once()) ->method('addCard') ->with($this->identicalTo($card)); $otherHand->expects($this->once()) ->method('removeCard') ->with($this->identicalTo($card)); $this->assertTrue($this->player->takeCards($otherPlayer, 4)); } }
How it works...
The getMock()
method is used to create a mock. The first parameter passed to this method is the name of the class or interface you are mocking. This method will inspect the class or interface you pass to it and will either extend it (if it is a class) or implement it (if it is an interface.) If no other parameters are given, any method that is not declared as private, final, or static will be overridden to simply return null. You can also pass an array of methods in the mocked class that you wish to override. This allows for creating partial mocks.
A couple of the getMock()
calls in the example also pass an array of values as the third parameter. These values are used to construct the mock object. When mock objects for classes are created, the original constructor of that class is called by default. If you do not pass the parameters you would like to use to the constructor, you will get an error similar to the one shown as follows:
If your tests do not need to utilize the values in the constructor then you can prevent the mock object from calling the original constructor. This can be accomplished by setting the fifth parameter of getMock()
to false
.
The testTakeCardFromPlayer()
method in the preceding example could be modified to create its mocks as shown next and the test will continue to run with no problem. You should prevent constructor calls whenever possible.
public function testTakeCardFromPlayer() { $otherHand = $this->getMock('CardCollection'); $otherPlayer = $this->getMock('Player', array(), array(), '', false); $card = $this->getMock('Card', array(), array(), '', false); // Continue method... }
You may be wondering now what that fourth parameter is for. The fourth parameter of getMock()
allows you to specify the name for the new mock class. This is not something that is typically necessary. If you need to skip the constructor and do not want to create a custom name for the new mock class you can simply pass an empty string.
Once the mock is created you will typically need to either stub a method or create expectations for methods on that mock. In testDrawCard()
we just needed to ensure that the moveTopCardTo()
method was being called with the appropriate arguments. If you want to set an expectation such as this you call the expects()
method on your mock. The expects()
method takes an invocation matcher as its argument. The best way to think about invocation matchers is as a counter of when or how often a method should be called. The PHPUnit_Framework_TestCase
class has several methods that can be passed to expects()
such as any()
, never()
, atLeastOnce()
, once()
, exactly()
, and at()
. All of these methods with the exception of at()
are used to indicate the number of times you are expecting the method being mocked to be a call. So if you specify once()
then the method you are calling can only be called once. If it is not called or is called multiple times then your expectation will not be met and your test will generate a failure similar to the following screenshot:
Once you have set your expectation you must specify the method you are setting the expectation on. This is done by calling the method()
method on the result of expects()
. The only parameter of this method is the name of your mock object's method that you want to set expectations on.
The final step in defining an expectation on a mock is to set the expected parameters. This is done by calling the with()
method. You can see the with()
method being called with a single argument in the testDrawCard()
method. You will pass as many arguments to with()
as you would see passed to the method you are mocking. By default PHPUnit will attempt to verify that the argument passed to with()
is equal to the argument passed to the mocked method. You can also use any of PHPUnit's constraints to check for other aspects of each argument. For instance, in testTakeCardFromPlayer()
you will see that the identicalTo()
method is being used so we can validate the exact instance of an object. Any assertions that are defined in PHPUnit will also have a constraint counterpart that can be used when mocking objects.
If you want to control the return value for the method that you are mocking you can use the will()
method in addition to the with()
method. The will()
method takes a single parameter that allows you to specify what will be returned from the method. This is done using one of PHPUnit's stub methods. The most common method and the one that is used in the example is returnValue()
. This allows you to specify the specific value that the method should return.
There's more...
Mock objects can be used for much more than simple stubbing and verifications. Now that you are aware of the basics we can discuss some other common uses of mock objects. You can find thorough documentation on PHPUnit's mock object functionality at http://phpunit.de/manual/current/en/test-doubles.html.
Thoughts on partial mocks
You should avoid partial mocks whenever possible. They tend to contribute to hard-to-maintain tests as you will often find yourself having to adjust the methods being mocked. A common use of partial mocks is to prevent a protected method from executing in the class you are testing. This usually indicates that you have one class doing too much. That protected method would likely be better served as a method in a separate class that you can then create a full mock for. Another use for partial mocks is to mock abstract classes. This is actually a fairly good use of partial mocks; however, there is a better way to handle this that will keep your tests easy to maintain. We will discuss this later.
Ignoring parameters on method expectations
Occasionally, you won't really care about what or how many parameters were used to call a method. When this is the case you can use the withAnyParameters()
method instead of with()
. This will essentially match any call to the given method.
Stubbing exceptions
When you find yourself writing tests for exception handling, you will find it necessary to stub a method to throw an exception. This can be done using PHPUnit's throwException()
method. This is very useful in helping to make sure you are handling exceptions in third-party code (or even your own code) gracefully. It takes an instantiated exception as its only argument.
public function testThrowException() { $card = $this->getMock('Card', array(), array(), '', false); $card->expects($this->any()) ->method('getNumber') ->will($this->throwException(new RuntimeException('Test Exception'))); // verify that the exception above is thrown. $this->setExpectedException('RuntimeException', 'Test Exception'); $card->getNumber(); }
Stubbing multiple return values
Occasionally, you will need a method to return one of many values. There are a few ways this can be handled. The first way is with a return value map. A return value map specifies a list of arrays that contains a value for every parameter passed to the mocked method and an additional value at the end for the return value. If a set of parameters do not exist in the map then null is returned. You will notice we are using the any()
matcher as we know the method is going to be called multiple times.
public function testReturnValueMap() { $calculator = $this->getMock('TestCalculator'); $valueMap = array( array(1, 2, 3), array(2, 4, 6), array(1, 4, 5) ); $calculator->expects($this->any()) ->method('add') ->will($this->returnValueMap($valueMap)); // Test Return Values $this->assertEquals(3, $calculator->add(1, 2)); $this->assertEquals(6, $calculator->add(2, 4)); $this->assertEquals(5, $calculator->add(1, 4)); $this->assertNull($calculator->add(1,3)); }
If the order of a specific set of calls is well defined, then you can use the onConsecutiveCalls()
method. This method accepts any number of arguments and will return each one in order for every call made to the mocked method. If you do not have as many arguments as there are method calls, then it will begin returning null after there are no more arguments left to return.
This is a very effective way to test code utilizing an iterator-like interface. For instance, given code such as the following:
while (!$this->game->isOver()) { // ... }
You can guarantee that the loop is executed twice using the following mock definition:
$mock->expects($this->any()) ->method('isOver') ->will($this->onConsecutiveCalls(false, false, true));
Stubbing with callbacks
Perhaps the most flexible thing you can do when creating a stub is using a callback. This allows you to define an anonymous function to generate a return value. While this can get complicated, it does give you the ability to simplify tests if you use it wisely.
If the addCard()
method was responsible for returning the current size of the collection after the card was added, and we tested a method that relied on that behavior, one way we could implement it is using a callback.
public function testReturnCallback() { $deck = $this->getMock('CardCollection'); $deck->expects($this->any()) ->method('addCard') ->will($this->returnCallback(function (Card $card) { static $collectionSize = 0; $collectionSize++; return $collectionSize; })); // Test Return Values $this->assertEquals(1, $deck->addCard(new Card('A', 'Hearts'))); $this->assertEquals(2, $deck->addCard(new Card('2', 'Hearts'))); $this->assertEquals(3, $deck->addCard(new Card('3', 'Hearts'))); }
Using mock builders
In our examples so far we have used PHPUnit's original mock functionality. PHPUnit 3.5 introduced a concept called MockBuilder
. The purpose of MockBuilder
is to clean up the instantiation of mock objects. As we have already discussed, the parameters of getMock()
can get very confusing. You can use the mock builder to try and make your tests more readable. Each of the various arguments we specified for getMock()
are represented by separate methods on the builder. For instance, the ability to disable the constructor can instead be enabled by calling disableOriginalConstructor()
on your builder. An example of how testTakeCardFromPlayer()
could benefit from this feature when creating mocks can be seen as follows:
public function testTakeCardFromPlayer() { $otherHand = $this->getMock('CardCollection'); $otherPlayer = $this->getMockBuilder('Player') ->disableOriginalConstructor() ->getMock(); $card = $this->getMockBuilder('Card') ->disableOriginalConstructor() ->getMock(); // ... }
Using alternative mock frameworks
While PHPUnit's mock framework provides a significant amount of functionality there are other libraries that can be used in conjunction with PHPUnit that provide a more robust feature set. In many cases, despite this robustness, the libraries are also easier to use.
Phake
Phake is an alternative mocking framework to PHPUnit's built-in mocking framework. The primary motive behind its creation was to present an alternative to the concept of expectations that PHPUnit utilizes. It, instead, treats mock object expectations as assertions that you execute after your test code has run, utilizing Phake's verification framework. An example of how PlayerTest
can be rewritten using Phake is shown as follows:
<?php class PhakePlayerTest extends PHPUnit_Framework_TestCase { private $player; /** * @Mock CardCollection */ private $hand; public function setUp() { Phake::initAnnotations($this); $this->player = new Player('John Smith', $this->hand); } public function testDrawCard() { $deck = Phake::mock('CardCollection'); $this->player->drawCard($deck); Phake::verify($deck) ->moveTopCardTo($this->identicalTo($this->hand)); } public function testTakeCardFromPlayer() { $otherHand = Phake::mock('CardCollection'); $otherPlayer = Phake::mock('Player'); $card = Phake::mock('Card'); Phake::when($otherPlayer) ->getCard(Phake::anyParameters())->thenReturn($card); Phake::when($otherPlayer) ->getHand()->thenReturn($otherHand); $this->assertTrue($this->player->takeCards($otherPlayer, 4)); Phake::verify($this->hand) ->addCard($this->identicalTo($card)); Phake::verify($otherHand) ->removeCard($this->identicalTo($card)); } }
An exhaustive discussion of Phake is outside the scope of this book. However, you can learn more about Phake at https://github.com/mlively/Phake.
Mockery
Another alternative mock object framework is Mockery. It is similar in concept to PHPUnit's own mock framework but makes some adjustments to its API to make what is being done by the code more clear to the readers. An example of PlayerTest
written with Mockery is shown as follows:
<?php class MockeryPlayerTest extends PHPUnit_Framework_TestCase { private $player; private $hand; public function setUp() { $this->hand = \Mockery::mock('CardCollection'); $this->player = new Player('John Smith', $this->hand); } public function testDrawCard() { $deck = \Mockery::mock('CardCollection'); $deck->shouldRecieve('moveTopCardTo') ->with($this->identicalTo($this->hand)); $this->player->drawCard($deck); } public function testTakeCardFromPlayer() { $otherHand = \Mockery::mock('CardCollection'); $otherPlayer = \Mockery::mock('Player'); $card = \Mockery::mock('Card'); $otherPlayer->shouldReceive('getCard') ->with(4) ->andReturn($card); $otherPlayer->shouldReceive('getHand') ->andReturn($otherHand); $this->hand->shouldReceive('addCard') ->with($this->identicalTo($card)); $otherHand->shouldReceive('removeCard') ->with($this->identicalTo($card)); $this->assertTrue($this->player->takeCards($otherPlayer, 4)); } }
For more information about Mockery see https://github.com/padraic/mockery.