Federico Cargnelutti / Wiki

PHPUnit: Testing Zend Framework Controllers

From Federico Cargnelutti

Jump to: navigation, search

Contents

Introduction

Testing a Web application is a complex task, because a Web application is made of several layers of logic. Unit testing a Zend Framework controller can be very difficult, specially for those who are not familiar with the Zend Framework.

You can test your action controllers using Zend_Test and/or PHPUnit. Zend_Test allows you to simulate requests, insert test data, inspect your application’s output and generally verify your code is doing what it should be doing. It's up to you to decide which one to use. Even though this choice can be confusing, you can use both. If you're just getting started with testing, using Zend_Test will probably get you started faster.

The PHPUnit framework will probably feel very familiar to developers coming from Java. It’s inspired by Java's JUnit, so you'll feel at home with this method if you've used JUnit or any test framework inspired by JUnit. Of course, no one stops you from using both systems side-by-side (even in the same app). In the end, most projects will eventually end up using both. Each shines in different circumstances.

Using PHPUnit

First, you need to create the directory structure:

app/
    config/
    controllers/
        ExampleController.php
    models/
    views/
lib/
    Zend/
public/
tests/
    controllers/
        AllTests.php
        ExampleControllerTest.php
    lib/
    AllTests.php
    bootstrap.php

A test suite needs some environmental information, and this information is usually found in the bootstrap.php file. The biggest difference between this file and the one you use in you application, is that the Front Controller doesn't dispatch the Request Object:

tests/bootstrap.php

/* Start output buffering */
ob_start();
 
/* Report all errors directly to the screen for simple diagnostics in the dev environment */
error_reporting( E_ALL | E_STRICT );
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
date_default_timezone_set('Europe/London');
 
/* Determine the root and library directories of the application */
$appRoot = dirname(__FILE__) . '/..';
$libDir = "$appRoot/lib";
$path = array($libDir, get_include_path());
set_include_path(implode(PATH_SEPARATOR, $path));
 
define('APPLICATION_PATH', $appRoot . '/app');
define('APPLICATION_ENVIRONMENT', 'dev');
 
require_once "Zend/Loader.php";
Zend_Loader::registerAutoload();
 
$front = Zend_Controller_Front::getInstance();
$front->throwExceptions(true);
$front->setParam('noViewRenderer', true);
$front->setParam('env', APPLICATION_ENVIRONMENT);
$front->setRequest(new Zend_Controller_Request_Http());
$front->returnResponse(true);
 
$router = $front->getRouter();
include APPLICATION_PATH . '/config/routes.php';
$router->addRoutes($routes);
$router->setParams($front->getParams());
 
$dispatcher = $front->getDispatcher();
$dispatcher->setParams($front->getParams());
$dispatcher->setResponse($front->getResponse());
 
$router->route($front->getRequest());

Please note that disabling the ViewRenderer helper is optional. However, you should know that using the Zend_Controller_Action_Helper_ViewRenderer class can result in performance degradation. More info here.

The PHPUnit_Framework_TestSuite class of the PHPUnit framework allows you to organize tests into a hierarchy of test suites:

tests/AllTests.php

require_once dirname(__FILE__) . '/bootstrap.php';
require_once dirname(__FILE__) . '/controllers/AllTests.php';
 
class AllTests
{
    public static function main()
    {
        $parameters = array();
        PHPUnit_TextUI_TestRunner::run(self::suite(), $parameters);
    }
 
    public static function suite()
    {
        $suite = new PHPUnit_Framework_TestSuite('My Application');
        $suite->addTest(ControllersAllTests::suite());
        return $suite;
    }
}
 
AllTests::main();

tests/controllers/AllTests.php

require_once dirname(__FILE__) . '/ExampleControllerTest.php';
 
class ControllersAllTests
{
    public static function main()
    {
        PHPUnit_TextUI_TestRunner::run(self::suite());
    }
 
    public static function suite()
    {
        $suite = new PHPUnit_Framework_TestSuite('My Application - Controllers');
        $suite->addTestSuite('ExampleControllerTestCase');
        return $suite;
    }
}

Writing unit tests

For some strange reason this part is not documented in the manual. Here's what you need to do before writing a test:

  • Include the controller you want to test.
  • Extend the Action controller.
  • Reset the instance of the Front controller.
  • Set the path to the Example action controller.
  • Set the Request and Response objects.
  • Create an instance of the Example action controller.

Example:

tests/controllers/ExampleControllerTest.php

require_once APPLICATION_PATH . '/controllers/ExampleController.php';
 
class ExampleControllerTest extends ExampleController
{
    public function __construct($url = null)
    {
        $front = Zend_Controller_Front::getInstance();
        $front->resetInstance();
        $front->setControllerDirectory(APPLICATION_PATH . '/controllers');
        $front->setRequest(new Zend_Controller_Request_Http($url));
        $front->setResponse(new Zend_Controller_Response_Http());
 
        parent::__construct($front->getRequest(), $front->getResponse());
    }
}

All the magic happens inside the ExampleControllerTest class. It makes the Action controller believe it's being dispatched by the Front controller. This is the only way to create an instance of the action controller without dispatching the request. Getting an instance of the action controller gives you more control and flexibility, specially when testing web services.

That's it, time to create our first test suite. A test suite is a class inherited from PHPUnit_Framework_TestCase containing test methods, identified by a leading "test" in the method name.

require_once APPLICATION_PATH . '/controllers/ExampleController.php';
 
class ExampleControllerTest extends ExampleController
{
    ...
}
 
class ExampleControllerTestCase extends PHPUnit_Framework_TestCase
{
    public function testDefaultAction()
    {
        $controller = new ExampleControllerTest();
        $isDispatched = $controller->indexAction();
 
        $this->assertTrue($isDispatched);
    }
 
    public function testFirstAction()
    {
        $url = 'http://localhost/example/first';
        $controller = new ExampleControllerTest($url);
        $controller->firstAction();
        $errorMsg = $controller->getRequest()->getParam('error_message', null);
 
        $this->assertEquals(null, $errorMsg);
    }
 
    public function testGetParameterName()
    {
        $url = 'http://localhost/example/first/fed';
        $controller = new ExampleControllerTest($url);
        $name = $controller->getRequest()->getParam('name', null);
 
        $this->assertEquals('fed', $name);
    }
 
    public function testGetNameMethod()
    {
        $url = 'http://localhost/example/first/fed';
        $controller = new ExampleControllerTest($url);
        $name = $controller->getName();
 
        $this->assertEquals('fed', $name);
    }
}

Running tests

federico@tests$ phpunit AllTests
PHPUnit 3.3.8 by Sebastian Bergmann.
.....
Time: 0 seconds
OK (4 tests, 4 assertions)

If there are test failures, you'll see full details about which tests failed. Optionally, you can plug Phing into Hudson and automate this task. Happy testing!

Personal tools