Using Zend_Test for Web Services

Recently I had cause to develop a web service and so I wrote some tests to go along with it - or I was about to. When I looked at the asserts available in Zend_Test, they were all geared towards HTML/CSS output - but we can test just as effectively on another output.

Using Zend_Test, I set up the request object I wanted to send, and dispatched it. Then I retrieved the body and, since this service returns JSON, I json_decoded it. This gives me an object - and I can go ahead and use all the functionality of PHPUnit, with or without Zend_Test's additions, to test my service. Its perhaps easiest to show this in a few steps.

Setting up the request object

The idea here is that you set up any parameters you need to including the HTTP verb to use and cookies if needed, then despatch the request. Here's a few examples, first a simple GET method, with a cookie.

        $request = $this->getRequest();
        $request->setMethod('GET');
        $request->setCookie('token','xxxx');
        $this->dispatch('/user/24');

Including data with a POST request:

        $request = $this->getRequest();
        $request->setMethod('POST');
        $request->setPost(array(
            'name' => 'new user',
            'organisation' => 49
            ));
        $this->dispatch('/user');

This is a REST service, so I also tested PUT and DELETE methods. DELETE just needs the setMethod() call since it doesn't have any data with it, but PUT was a bit trickier - here's an example of what I used:

        $request = $this->getRequest();
        $request->setMethod('PUT');
        $params = array('name' => 'Harry Potter');
        $request->setRawBody(http_build_query($params));
        $this->dispatch('/user/48');

Decoding the Response

This is the easy part, all I do is check the status code is what was expected, and then decode the response. My web service returns JSON so this part of each test looks something like:

        $this->assertResponseCode('200');
 
        $response = json_decode($this->getResponse()->getBody());

Testing the content of the response

Here we get into classic PHPUnit territory and simply use the assertTrue and assertEquals calls we'd use when testing anything else, an example is included for completeness:

        $this->assertEquals($response->contentType, 'user');
        $this->assertTrue(is_numeric($response->id));
        $this->assertEquals($response->name, 'new user');
        $this->assertEquals($response->organisation, 49);

In Conclusion

By combining the request/response awareness of Zend_Test with standard PHPUnit testing strategies, its easy to test web services as well as web pages. I hope the examples are helpful - if they help you or if you have anything to add, then leave a comment!

PHPUnit with Zend_Controller_Action_Helper

I'm currently working on a REST service built in Zend Framework and I ran into problems very quickly - when I tried to write the first unit test for the first action in fact! PHPUnit was just dying when I asked it to dispatch() any URL which didn't return HTML, it wasn't even giving its usual output.

What was actually happening was I was making use of Zend_Controller_Action_Helper_Json, my service returns JSON and this action helper takes input, transforms it into JSON, sets the content-type correctly and tells ZF not to look for a view since we don't need one. I thought this was pretty neat.

But look at the start of the declaration for the action helper:

class Zend_Controller_Action_Helper_Json extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * Suppress exit when sendJson() called
     * @var boolean
     */
    public $suppressExit = false;
...

By default the JSON action helper will exit() - which of course when run from phpunit causes that to exit as well! There is the class variable there so it was simple to turn off - I just extended my class and changed the value of that $suppressExit variable.

class Zend_Controller_Action_Helper_ServiceJson extends Zend_Controller_Action_Helper_Json {
    public $suppressExit = true;
}

My tests now run successfully and I can build my application, hopefully next time I'll realise what I'm doing wrong faster!