Pragmatism in the real world

Unit testing controller actions with Zend_Test_PHPUnit_ControllerTestCase

Testing controllers has traditionally been a hassle due to the requirements of setting up the bootstrap, the front controller and initiating the dispatch cycle. In June, Matthew addressed this with the release of Zend_Test_PHPUnit_ControllerTestCase way back in 2008.

Later, Matthew helpfully wrote an article on how to use it and I have used that as a starting point for the information here. (Thanks Matthew!)

The project I’m using is TodoIt, which is a simple ZF demo application, which needs unit tests.

Setting up PHPUnit

All your unit tests will live in the /tests folder. The ZF cli tool will create a phpunit.xml file for you, but you’ll discover that it’s empty! This is what it should look like:


<phpunit colors="true" bootstrap="./TestHelper.php">
<testsuite name="TodoIt Test Suite">
<directory>./</directory>
</testsuite>

<filter>
<whitelist>
<directory suffix=".php">../library/</directory>
<directory suffix=".php">../application/</directory>
<exclude>
<directory suffix=".phtml">../application/</directory>
</exclude>
</whitelist>
</filter>

<logging>
<log highlowerbound="80" lowupperbound="50" highlight="true" yui="true" charset="UTF-8" target="./log/report" type="coverage-html"></log>
</logging>

</phpunit>

This file is used to configure phpunit itself and saves having to use command line options. As it’s XML, it’s fairly easy to read. The testsuites element is used to specify the testsuite we’re going to test. In principle you can have many test suites; in this case, one is enough! The filter section is used to specify which files to use for code coverage reporting and the logging section is used to configure the reports.

We also specify TestHelper.php as the bootstrap. This mean that it is called for us and contains the necessary PHP setup we need to do in order to load and use Zend Framework. In effect TestHelper.php acts like public/index.php does for your web application. TestHelper.php looks like this:


<?php
// Based on http://weierophinney.net/matthew/archives/190-Setting-up-your-Zend_Test-test-suites.html

// PHP settings
error_reporting(E_ALL | E_STRICT);
date_default_timezone_set('Europe/London');

define('APPLICATION_ENV', 'unittesting');
define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));

// Directories for include path
$root = realpath(dirname(__FILE__) . '/../');
$library = $root . '/library';
$models = $root . '/application/models';

$path = array(
$library,
$models,
get_include_path()
);
set_include_path(implode(PATH_SEPARATOR, $path));

require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance();

// Unset global variables
unset($root, $library, $models, $path);

As with public/index.php, we set APPLICATION_ENV and APPLICATION_PATH, update the include_path and then set up the autoloader. Now we’re all ready to write some tests!

A controller test class

I place my controller test classes in tests/application/controllers to make them easy to find. (Model tests go in tests/application/models!). The TodoIt application has a login form in AuthController::indexAction() which is accessed via the /auth URL. We’ll start by testing this form is displayed.

The controller’s test class is called AuthControllerTest and lives in tests/application/controllers/AuthControllerTest.php:


<?php

// Call AuthControllerTest::main() if this source file is executed directly.
if (!defined("PHPUnit_MAIN_METHOD")) {
define("PHPUnit_MAIN_METHOD", "AuthControllerTest::main");
}

require_once 'PHPUnit/Framework/TestCase.php';

/**
* @group Controllers
*/
class AuthControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public static function main()
{
$suite = new PHPUnit_Framework_TestSuite(get_class($this));
$result = PHPUnit_TextUI_TestRunner::run($suite);
}

public function setUp()
{
$application = new Zend_Application(
APPLICATION_ENV,
APPLICATION_PATH . '/configs/application.ini'
);

$this->bootstrap = $application;
return parent::setUp();
}

public function tearDown()
{
/* Tear Down Routine */
}

public function testLoginDisplaysAForm()
{
$this->dispatch('/auth/index');
$this->assertQueryContentContains('h1', 'Login');
$this->assertQuery('form#login'); // id of form
}
}

There’s three things going on in here, so let’s look at each in turn.

Firstly we set up the file to allow PHPUnit to run this file on it’s own using the command line:

phpunit testsapplicationcontrollersAuthControllerTest.php

This is done by setting the PHPUnit_MAIN_METHOD constant to the static method AuthControllerTest::main(). The phpunit cli tool will then run this method which will in turn run this file as a test suite.

The methods setUp() and tearDown() are called before and after every test method and are used to ensure that we have a clean slate for each one. As we extended from Zend_Test_PHPUnit_ControllerTestCase rather than from PHPUnit_Framework_TestCase, we are able to leverage functionality specifically designed to make testing controllers easier. We use this in setUp() to set the property bootstrap to an instance of Zend_Application, which is then used in the tests themselves.

Each test is a method that starts with the word test, like this one:

public function testLoginDisplaysAForm()
{
$this->dispatch('/auth/index');
$this->assertResponseCode(200);
$this->assertQueryContentContains('h1', 'Login');
$this->assertQuery('form#login'); // id of form
}

We start by calling dispatch() to run the correct action and then we use the various assert methods to check that the result is what we expect. The assertResponseCode method checks that we didn’t error as the errorController will set the code to 500 or 404. We can then use the assertQuery methods to check what has been rendered to the response object. These use DOM paths to select a specific element. The call to assertQueryContentContains allows us to check the text within the H1 element is what we expect and the assertQuery just checks that the element is on the page.

That’s it.

This is just the tip of the iceberg and I strongly suggest that you have a read of the documentation to see for yourself how many different assertions you can use to check that your code is performing as expected.

4 thoughts on “Unit testing controller actions with Zend_Test_PHPUnit_ControllerTestCase

  1. Hi,

    first of all, thanks for this post. Testing simple zf applications now works like a charm.

    But some of my projects have a modular structure and therefor the models path can't be declared like you did in the TestHelper.

    Do you have any advice how to get this kind of project tested?

    Regards

    Marc

  2. Hi, great post, but I have a little problem, I can run AllTest and works well, but when I run a single file test dont find the class, like test without call TestHelper, any idea?

  3. I use Zend_Test often, but both what I do and what you describe here is not *unit* testing, but testing at the acceptance/functional level. It is however as important as unit testing since it exercise the whole Model/View/Controller parts together.

  4. Thanks for this great post, helped me a lot, i was struggling with some problems and in the #zftalk channel Bittarman pointed me out this post.

    You're always indirectly helping me, since i have beginning to work with Zend Framework.

    Waiting for the ZF 2 in Action.

Comments are closed.