Basically, I want to test the controller code in isolation from the front controller, the router, the dispatcher, the views, etc. All I to do is set up a request object, invoke the action methods of the controllers, and then assert against the variables assigned to the view. For these tests, I don't care about the output of the view templates themselves - I just want to know the controllers are putting the right variables into the view object.
It turns out this is actually pretty simple. I made a custom test case:
class Sfx_Controller_TestCase extends Sfx_TestCase
{
protected $_request;
protected $_response;
protected $_controller;
public function setUp()
{
parent::setUp();
// set up smarty view and restful view helper
$viewRenderer = new Sfx_Controller_Action_Helper_TestViewRenderer();
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
$this->_request = new Zend_Controller_Request_Http();
$this->_response = new Zend_Controller_Response_Cli();
}
}
Sfx_TestCase contains all my bootstrap code. However, the only thing I do in bootstrap is set include path and set up a default db adapter for Zend_Db_Table. I don't do anything with Zend_Controller_Front. So this may as well extend straight from PHPUnit_Framework_TestCase. I'm not sure why others are claiming you have to use Zend_Controller_Front to test ZF Controllers - you don't.
I wrote and use Sfx_Controller_Action_Helper_TestViewRenderer (and proposed it as a core class) to simply create an empty Zend_View object into which the controllers can assign variables. Here's the whole class:
class Sfx_Controller_Action_Helper_TestViewRenderer extends Zend_Controller_Action_Helper_ViewRenderer
{
public function initView()
{
if (null === $this->view) {
$this->setView(new Zend_View());
}
// Register view with action controller (unless already registered)
if ((null !== $this->_actionController) && (null === $this->_actionController->view)) {
$this->_actionController->view = $this->view;
}
}
}
With only this much plumbing, I'm able to test the Controllers in isolation - no worrying about routes, dispatchers, plugins, helpers, nor view templates - like so:
class ProjectControllerTest extends Sfx_Controller_TestCase
{
private function __constructProjectController()
{
return new ProjectController($this->_request, $this->_response);
}
public function test_indexAction_fetches_all_projects()
{
$this->_controller = $this->__constructProjectController();
$this->_controller->indexAction(); // assigns 'resources' to view
$this->assertNotNull($this->_controller->view->resources);
$this->assertEquals(27,count($this->_controller->view->resources));
}
public function test_indexAction_new_since_fetches_only_new_projects()
{
$this->_request->setParam('new_since',1205880839);
$this->_controller = $this->__constructProjectController();
$this->_controller->indexAction();
$projects = $this->_controller->view->resources;
$this->assertEquals(4,count($projects));
foreach($projects as $project){
$this->assertGreaterThan(1205880839, $project->create_time);
}
}
public function test_indexAction_limit_limits_projects()
{
$this->_request->setParam('changed_since', 1205880839);
$this->_request->setParam('order_by','changed_since');
$this->_request->setParam('limit', 5);
$this->_controller = $this->__constructProjectController();
$this->_controller->indexAction();
$projects = $this->_controller->view->resources;
$this->assertEquals(5,count($projects));
$prevChangeTime = 0;
foreach($projects as $project){
$this->assertGreaterThanOrEqual($prevRegTime, $project->change_time);
$prevChangeTime = $project->change_time;
}
}
}
I'm finding this to be a much simpler and easier way of testing ZF Controllers than the other articles I've been reading. Now if you want to test everything in the front controller dispatch process and the view templates, I think Zend_Test is the best bet, but I've not used it yet so I can't be sure. The above classes work fine for what I do.
1 comment:
Hi Luke.
Great article.
I tagged you in http://duodraco.wordpress.com/2009/01/09/seven-things-that-probably-you-may-not-know-about-me/ .
Post a Comment