In my previous column PHP Unit Testing with PHPUnit,I showed how to set up PHPUnit and how to run a few simple tests. You should be able to test any function or method now that doesn’t rely on making calls out to other methods or functions – which is of course very few, as most applications are a complex combination of methods and, in PHP’s case, functions.
Author: Kendrick Curtis, Stainless Software, http://www.stainless-software.com/
Writing unit tests for any application relies upon being able to isolate the object under test, which can be difficult in these situations. Here we’ll take a look at a few strategies for properly isolating the code that you are trying to test in order to prove that it works without having to test your entire system.
Depending on what you read, different distinctions are drawn between mock objects, fake objects and stubs. PHPUnit natively provides mock objects with enough functionality to supply both predetermined inputs to the code under test and also to verify whether certain methods were called.
Injecting Input With Mock Objects
Simply, a mock object is a “fake” version of a code object (usually an object in the OO sense as well). In order to isolate your object under test, it’s usually necessary to mock out all of the other objects that it interacts with. A mock object has the same interface as the real thing, but responds in a way pre-programmed by your test code. Let’s look at an example, a simple DataProcessor class that takes in a DataReader and performs a simple operation:
class DataReader
{
public function readData()
{
$json = file_get_contents("http://stainless-software.com/stm/json");
return $json;
}
}
class DataProcessor
{
public function process(DataReader $dr)
{
$json_data = $dr->readData();
$data = json_decode($json_data);
return $data;
}
}
In order to test the DataProcessor, we don’t want to have to worry about the operation of the DataReader; we will test that separately. So, instead of supplying a real DataReader, we will supply a mock instead, prepared to give a fixed value:
class DataProcessorTest extends PHPUnit_Framework_TestCase
{
function testDataProcessorProcess()
{
$mock_dr = $this->getMock("DataReader");
$mock_dr->expects($this->any())
->method("readData")
->will($this->returnValue('{"data": [4,5,6]}'));
$dp = new DataProcessor();
$result = $dp->process($mock_dr);
$this->assertInstanceOf("stdClass", $result);
$this->assertCount(3, $result->data);
$this->assertEquals(4, $result->data[0]);
$this->assertEquals(5, $result->data[1]);
$this->assertEquals(6, $result->data[2]);
}
}
The getMock method of PHPUnit_Framework_TestCase allows us to easily create a mock version of a real class. We configure the mock in a fluent style as shown. We want to mock out the “readData” method to provide a result as given instead of relying on the real code:
* ->expects: this takes on of the matchers as buried in the manual [1]. $this->any() means that the mock expects multiple calls to the method we’re about to specify. We’ll revisit how to use these for method call verification purposes later.
* ->method: is the name of the method to mock out.
* ->will: is what this method will return to the user when called.
Mocking ‘New’ Objects
So far so straightforward. One of the most common problems you’ll face with mock objects, though, is that the code under test looks more like this:
class DataProcessor
{
public function process()
{
$dr = new DataReader();
$json_data = $dr->readData();
$data = json_decode($json_data);
return $data;
}
}
It’s simply not possible to replace that $dr with a mock because the new object is created inside the method under test. Instead we have to replace the call to the creation of a new object with a call to a local method, and then create a test version of our object under test, overriding that creation method. So the program code ends up looking like this:
class DataProcessor
{
protected function _newDataReader()
{
return new DataReader();
}
public function process()
{
$dr = $this->_newDataReader();
$json_data = $dr->readData();
$data = json_decode($json_data);
return $data;
}
}
In order to test the process() method now, we need to make a few changes to our testing code. We have two options here: we could subclass DataProcessor into a TestableDataProcessor like this:
class TestableDataProcessor extends DataProcessor
{
public $mock_dr;
protected function _newDataReader()
{
return $this->mock_dr;
}
}
class DataProcessorTest extends PHPUnit_Framework_TestCase
{
function testDataProcessorProcess_Subclass()
{
$mock_dr = $this->getMock("DataReader");
$mock_dr->expects($this->any())
->method("readData")
->will($this->returnValue('{"data": [4,5,6]}'));
$dp = new TestableDataProcessor();
$dp->mock_dr = $mock_dr;
$result = $dp->process();
$this->assertInstanceOf("stdClass", $result);
$this->assertCount(3, $result->data);
$this->assertEquals(4, $result->data[0]);
$this->assertEquals(5, $result->data[1]);
$this->assertEquals(6, $result->data[2]);
}
}
The other option is to use PHPUnit’s mocking functionality to produce a mock of the object under test itself and replace the _newDataReader that way. This is a little counterintuitive, but we can use access to the MockBuilder class to configure the mock of the object under test to only replace the one method we need to override:
class DataProcessorTest extends PHPUnit_Framework_TestCase
{
function testDataProcessorProcess_Mock()
{
$mock_dr = $this->getMock("DataReader");
$mock_dr->expects($this->any())
->method("readData")
->will($this->returnValue('{"data": [4,5,6]}'));
$dp = $this->getMockBuilder("DataProcessor")
->setMethods(array("_newDataReader"))
->getMock();
$dp->expects($this->once())
->method("_newDataReader")
->will($this->returnValue($mock_dr));
$result = $dp->process();
$this->assertInstanceOf("stdClass", $result);
$this->assertCount(3, $result->data);
$this->assertEquals(4, $result->data[0]);
$this->assertEquals(5, $result->data[1]);
$this->assertEquals(6, $result->data[2]);
}
}
Verifying Method Calls With Mock Objects
As we encountered earlier, when creating a mock method we are required to specify how many times a method is expected to be called by the code under test. We can utilize this to ensure that the code under test is making the calls that we expect during its operation. Mock objects automatically check the condition of their called methods as part of the test evaluation, so we don’t even need to add any more code to enable this functionality.
We can very quickly play with this using our previous example code. Replace the $this->any() with $this->exactly(2), and run the test. You’ll see it fail. Replace it with $this->once(), and it will pass again.
Alone, this isn’t very interesting. What would be really useful is to be able to verify any inputs sent into the mock object. Let’s modify our original program to take the URL to the JSON we want to download as a parameter:
class DataReader
{
public function readData($url)
{
$json = file_get_contents($url);
return $json;
}
}
class DataProcessor
{
protected function _newDataReader()
{
return new DataReader();
}
public function process()
{
$dr = $this->_newDataReader();
$json_data = $dr->readData("http://stainless-software.com/stm/json");
$data = json_decode($json_data);
return $data;
}
}
Taking the mocking route for overriding our object under test’s old _newDataReader method means that to verify that the DataProcessor’s process method actually calls the correct URL only requires adding one line of code:
$mock_dr->expects($this->once())
->method("readData")
->with($this->equalTo("http://stainless-software.com/stm/json"))
->will($this->returnValue('{"data": [4,5,6]}'));
Verification Of Multiple Calls
Any moderately complex testing project will require you to cover the case in which an object under test calls the same method on a different object with different parameters each time. Stubbing out that method to both provide dummy responses for multiple calls and also to verify that the input parameters to those calls is relatively straightforward.
For managing different return values simply use the returnValueMap method as detailed in the PHPUnit manual. You should note that a valid map is all the input parameters to the method to be stubbed, followed by the return value as a final additional entry in the map. So something like this:
$url1 = "http://stainless-software.com/stm/json";
$url2 = "http://stainless-software.com/stm/json2";
$mock_dr->expects($this->once())
->method("readData")
->will($this->returnValueMap(
array (
array($url1, '{"data": [1,2,3]}'),
array($url2, '{"data": [4,5,6]}')
)));
For verification of successive sets of input parameters, let’s look at a fuller example:
class DataProcessor
{
protected function _newDataReader()
{
return new DataReader();
}
public function process()
{
$dr = $this->_newDataReader();
$json_data1 = $dr->readData("http://stainless-software.com/stm/json");
$data1 = json_decode($json_data1);
$json_data2 = $dr->readData("http://stainless-software.com/stm/json2");
$data2 = json_decode($json_data2);
return true;
}
}
The key to testing the two similar method calls is the use of the ‘at’ matcher to specify the responses to the two calls and also add an additional expectation to the mock of the exact number of calls to be made. By using at($x), you ensure that the method is called with the arguments that you expect in the order that you expect, unlike the returnValueMap, which enforces no such ordering restriction.
class DataProcessorTester extends PHPUnit_Framework_TestCase
{
function testDataProcessorProcess_Mock()
{
$mock_dr = $this->getMock("DataReader");
$mock_dr->expects($this->exactly(2))
->method("readData");
$mock_dr->expects($this->at(0))
->method("readData")
->with($this->equalTo("http://stainless-software.com/stm/json"))
->will($this->returnValue('{"data": [1,2,3]}'));
$mock_dr->expects($this->at(1))
->method("readData")
->with($this->equalTo("http://stainless-software.com/stm/json2"))
->will($this->returnValue('{"data": [4,5,6]}'));
$dp = $this->getMockBuilder("DataProcessor")
->setMethods(array("_newDataReader"))
->getMock();
$dp->expects($this->once())
->method("_newDataReader")
->will($this->returnValue($mock_dr));
$result = $dp->process();
$this->assertTrue($result);
}
}
Conclusions
PHPUnit can be used to isolate your code under test from other parts of your application in order to test it without reliance on other pieces of code. This is useful because it breaks down the problem of testing your code into small, discrete chunks. Unit tests for code that interoperates with other pieces of code are also useful to help maintain the interface contracts between two pieces of code.
We’ve seen here how to simulate input and to verify output from a method that relies on other code. Tune in next time for some tips on how and when to use PHPUnit to be most effective, how to use PHPUnit when working with frameworks and how to use PHPUnit to test static methods.
References
[1] http://phpunit.de/manual/current/en/test-doubles.html
About the Author
Kendrick Curtis is a web developer with ten years experience. He is co-founder of Stainless Software, a freelance web design, development, testing and content authoring company. More info on http://www.stainless-software.com/