I posted this to my blog, but the code sections were ugly. I'm going to try posting here to see if it looks any nicer.
I've been playing around with Zend Framework, and while it has some idiosyncrasies, I'm enjoying it for the most part. I wanted to add testing onto the zf-tutorial app. For anyone to get anything out of this, they should go thru this "tutorial" over at the Alex @ Net blog.
Alex's work is an awesome starting point. It was, however, a bit of a chore to add on the kind of Controller "unit"-testing I like to do - i.e. constraint each test's code to the controller's action's alone, so that's the task I'm going to explain.
Though I graciously tip my hat to Alex for the advice of setting the tests directory to match the application directory, and to include all the bootstrap code in the AllTests.php suite file, I didn't like the way that the controller test code had lots of FrontController setup required. Nor do I particularly like the idea of "unit" tests that jump thru routing, dispatching, and view and only assert against view results. IMHO, "unit" testing controllers should be succinct and test only the controller code - not the FrontController logic, and not the view - and should make assertions on the assigned view variables instead of the view output. As you'll see, I didn't actually cut out the FrontController logic, I simply hid that code from my tests in a way that lets me get straight at the specific controller I'm testing. There seems to be some tight coupling between Action controllers and the Front controller in Zend Framework(?) so it has to stay.
So, to get on with my changes:
I added a BaseControllerTestCase class right next to my AllTests file. Sorry, my directory layout is a bit different from Alex's ...
/tests/BaseControllerTestCase.php
class BaseControllerTestCase extends PHPUnit_Framework_TestCase{
public $request;
public $response;
public $controllerClass;
public $actionMethod;
public function setUp(){
$this->frontController = Zend_Controller_Front::getInstance();
$this->response = new Zend_Controller_Response_Http();
$this->frontController->returnResponse(true)->setResponse($this->response);
}
protected function initController($url){
$this->request = $this->makeRequest($url);
$this->frontController->setRequest($this->request);
$dispatcher = $this->frontController->getDispatcher();
$controllerClass = $dispatcher->getDefaultControllerClass($this->request);
$this->actionMethod = $dispatcher->getActionMethod($this->request);
return new $controllerClass($this->request, $this->response);
}
protected function makeRequest($uri){
return new Zend_Controller_Request_Http($uri);
}
}
Then, I changed my tests to extend from this class so I can use the initController method which hides all the FrontController junk from my test code.
/tests/application/controllers/IndexControllerTest.php
class IndexControllerTest extends BaseControllerTestCase{
public function testIndexAction(){
// initialize controller using BaseControllerTestCase::initController
$controller = $this->initController('http://localhost/index/index');
// need to localize $this->actionMethod to dynamically call it by name on controller?
$actionMethod = $this->actionMethod;
// execute controller's specific action method
$controller->$actionMethod();
// get view's vars and make assertions against them
$viewVars = $controller->view->getVars();
$this->assertEquals("My Albums",$viewVars['title']);
$albums = $viewVars['albums'];
$this->assertTrue($albums instanceof Zend_Db_Table_Rowset);
$this->assertEquals(2,$albums->count());
}
}
That's it. It might be a little crude, but it gets the job done and it makes my test more concise and to-the-point, IMO.
As I said, I couldn't seem to totally de-couple the controller from the FrontController process completely. But I don't think that's as serious a flaw as testing the View and Controller in the same test. I'll probably write a BaseViewTestCase class in the future. If I ever feel like testing views, that is. 🙂