Here at the testomatic we’ve been using PHP 5 and the Zend Framework for a while now. Moving into the area of enterprise PHP development has presented some new challenges and differences in the area of testing. Of particular interest has been how to write code so that is unit testable using PHPUnit.
Unit testing is the process of ensuring that the code you have written behaves in the expected manner. The “unit” part means that we should only be testing the smallest possible feature of our codebase, isolating our tests around the individual concerns of a particular class or method.
In practice it is rare that we will write a class or method that in exists in isolation. More often it will rely on some other classes or methods to provide it’s functionality, these are what we call dependencies. Dependencies can come in various shapes and sizes, but a good rule of thumb to follow is the more complex functionality a dependency provides, the more important it is to remove it from the test equation.
Let’s look at a code example to illustrate this point further. The fictional class below acts as a gateway to a web service for sending emails.
<?php
class Email_Gateway {
protected $webService = 'https://mywebservice.com/email';
/**
* Sends an email
*/
public function sendEmail($to, $subject, $message) {
$httpClient = new Zend_Http_Client();
$httpClient->setUri($this->webService);
$httpClient->setParameterPost(array(
'to' => $to,
'subject' => $subject,
'message' => $message,
));
$response = $httpClient->request('POST');
if ($response->isError()) {
throw new Exception(
$response->getStatus()
. ' '
. $response->getMessage()
);
} else {
return true;
}
}
}
?>
This class adds a basic wrapper around a HTTP call to our service and throws an exception if it fails for some reason. In practice you might want a class such as this to do much more, such as validating that parameter is actualy an email, but it illustrates the point as is. The error handling part is the main piece of functionality given here, but because of the way we have written the code testing that our error handling works correctly becomes a bit cumbersome.
When testing this class in it’s current state we would have to be making HTTP calls and sending out emails, as well as ensuring the web service responded in the manner which we wanted to test against. That’s some pretty big external dependencies, and at the moment we are quite far away from testing our code in isolation.
One solution would be use a test fixture to represent our call to the web service. This means writing a fake HTTP client for use in our tests, it would pretend to talk to the web service and respond in the manner we want to test against. The current implementation of this class does not give us access to the Zend_Http_Client though, so in order to use our test with a fixture we need to refactor our code.
<?php
class Email_Gateway {
protected $webService = 'https://mywebservice.com/email';
protected Zend_Http_Client $httpClient;
/**
* Set our http client
*/
public function setHttpClient(Zend_Http_Client $httpClient) {
$this->httpClient = $httpClient;
}
/**
* Get our http client
*/
public function getHttpClient() {
if (!isset($this->httpClient)) {
$this->httpClient = new Zend_Http_Client();
}
return $this->httpClient;
}
/**
* Sends an email
*/
public function sendEmail($to, $subject, $message) {
$httpClient = $this->getHttpClient();
$httpClient->setUri($this->webService);
$httpClient->setParameterPost(array(
'to' => $to,
'subject' => $subject,
'message' => $message,
));
$response = $httpClient->request('POST');
if ($response->isError()) {
throw new Exception(
$response->getStatus()
. ' '
. $response->getMessage()
);
} else {
return true;
}
}
}
?>
In the revised class we have added some new methods, getters and setters for the http client. This allows us to replace our http client with a fixture object during our tests. I could have also inserted the http client via a constructor, but in this example I have gone for a setter and ‘lazy intialisation’ via the getter as it has minimal impact on the interface to the class.
This technique is a very basic example of what is know as dependency injection, and allows us to write our classes in a way that they become easily testable in isolation. A typical PHPUnit test for this class might now look like this:
<?php
public function testSendEmailSuccess() {
$mockHttpClient = $this->getMock('Zend_Http_Client');
$mockHttpClient->expects($this->any())
->method('isError')
->will($this->returnValue(false));
$emailGateway = new Email_Gateway();
$emailGateway->setHttpClient($mockHttpClient);
$result = $emailGateway->sendEmail('matt@foo.com',
'Hello',
'Hello Matt!'
);
$this->assertTrue($result);
}
?>
Here I have used one of PHPUnit’s mock objects, but a similar effect can be achieved by writing your own version of the HTTP client and injecting that instead. Martin Fowler describes the philosophical differences of these two approaches in quite a lot of detail, but in my so far limited experience I have found PHPUnit’s mock objects a convenient way to stub out dependencies in my tests.
The more I have come to use these coding practices in my own work, the more I have come to recognise them in the Zend Framework. Using the Zend_View_Helper component as an example, here’s my take on what the Zend developers might have been thinking when they designed some of it’s implementation.
Let’s say we’ve created a blog using the framework and in the side bar of each page would like to dynamically pull in a list of our recent flickr uploads as thumbnail images. This functionality might well be implemented as a view helper so in our templates all we need to do is:
<?php
echo $this->flickrImages();
?>
That keeps our view templates nice and clean and means we can share the flickr code across them. Behind the scenes the view helper would probably need to connect to the flickr API, find the recent uploads and return a list of image URLs. Then the helper might format them into a nice HTML list.
So here we have introduced a pretty big dependency in the view on a HTTP call to the flickr API and all the associated classes that go with it. This might become a bit of a problem when writing some unit tests for our controller using Zend_Test_PHPUnit_ControllerTestCase, as we don’t necessarily want to talk to flickr each time the tests are ran.
However, because we have implemented this functionality as a view helper, as opposed to something like $flickrImages = new FlickrImages(); we have already mitigated the dependency. By using the same tools the Zend Framework gives us for creating normal view helpers, we can inject a test fixture into our application when we are unit testing.
We can create a fixture view helper somewhere in our tests directory.
<?php
/**
* A test fixture for the flickr images view helper
*/
class MyApp_Test_View_Helper_FlickrImages {
public function flickrImages() {
return '<ul><li>Fake stuff</li></ul>';
}
}
?>
And load that when we bootstrap our controller unit tests.
<?php
$view = new Zend_View();
$view->addHelperPath('/path/to/real/helpers', 'MyApp_View_Helper');
$view->addHelperPath('/path/to/test/helpers', 'MyApp_Test_View_Helper');
?>
Because Zend View looks first in the most recently added path for the requested helper class it will find the fixture helper before the real one. A similar technique can be employed with action helpers to inject dependencies into your controllers. Dependency injection patterns such as this are interspersed throughout the Zend Framework and once realised can make unit testing your application’s components a lot easier.
What are your thoughts? Have you found any interesting dependency injection patterns or techniques in the Zend Framework? Why not share your thoughts below.
Comment
blog comments powered by Disqus