Contents |
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.
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.phpA 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; } }
For some strange reason this part is not documented in the manual. Here's what you need to do before writing a test:
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); } }
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!