- Part 1 – Getting Started
- Part 2 – Testing Controllers
- Part 3 – Mocks and Stubs
- Part 4 – Wrapping up the controller
In the first part of this series we set up our Zend Framework application and specified our requirements. In this part we’ll tackle the first requirement – as a user, I want to visit the home page and see a form where I can enter my full name and email address.
As a first test, I’ll make sure I can ‘GET’ the home page. I do this by testing the Index controller’s Index action. I have shown my preferred test directory structure below but here again the Zend Framework has no set structure.
tests |--application |--controllers |--models |--library |--TestHelper.php |--phpunit.xml
The test helper file was covered in the previous part and the other folders should be self explanatory. The phpunit.xml file is there to help load my test suite and basically contains directives that would otherwise need to be passed as command line arguments when running the tests. Mine is a really simple version and looks like:
<?xml version="1.0" encoding="UTF-8"?> <phpunit colors="true" verbose="true"> <testsuite name="All Tests"> <directory>./</directory> </testsuite> </phpunit>
An alternative way to load the test suite would be to add an AllTests.php file and add the tests to the suite in this file.
The first thing I’ll do is create a controller test file named IndexControllerTest.php in the controllers directory with the following content.
<?php require_once realpath(dirname(__FILE__) . '/../../TestHelper.php'); class IndexControllerTest extends BaseControllerTestCase { public function testIndexAction() { $this->dispatch('/'); $this->assertController('index'); $this->assertAction('index'); } }
This ensures we can get the home page and that the home page uses the index controller and index action. Most likely the action would have already been generated and if we now run the test suite (go into the tests folder and type ‘phpunit’), we should get 1 passing test.
Notice that the test class inherits from the abstract BaseControllerTestCase class in the Test Helper file. This removes the need for the setup() method in the actual test class and we don’t have to redo the Zend_Application setup in every controller test case. Of course, if we need to do any test case specific setup, we can always override or add to the the setup() method.
With controllers, it’s also about the HTTP response – did the action redirect or not and what was the response code? I will add a test for that to the IndexController test case.
public function testGetttingIndexActionShouldBeSuccessful() { $this->dispatch('/'); $this->assertResponseCode(200); $this->assertNotRedirect(); }
We should now get 2 passing tests when we run the test suite.
The next test I’ll add will verify that we indeed have a form with the fullname and email fields on the home page. I know we don’t but since I’m doing TDD I’ll write the tests anyway and expect them to fail. I’ll add the test for the form first.
public function testShouldShowFormOnHomePage() { $this->dispatch('/'); $this->assertQuery('form[action*="subscribe"]'); $this->assertQuery('form[id="subscribe-form"]'); }
Here I’m asserting (using the CSS Selector Assertions) there’s a form on the home page with ‘subscribe-form’ as its id and with subscribe as part of the action attribute – I intend to add a subscribe action to process the post.
We should now get one failing test and we need to add the requisite form tag to the home page as well as a subscribe action method to get all our tests passing. I’ll use Zend_Form for that and the Index controller now becomes:
public function indexAction() { $form = new Zend_Form(); $this->view->form = $form; } public function subscribeAction() { }
with this corresponding index view file:
<h1>Subscribe to our newsletter</h1> <?php $this->form->setAttrib('id', 'subscribe-form'); $this->form->setAction($this->url( array('action' => 'subscribe', 'controller' => 'index') )); ?> <?php echo $this->form; ?>
We should now get all our tests passing once again.
To verify the form elements I’ll use the Zend_Test XPath Assertions (as an alternative to the CSS Selector Assertions):
public function testShouldShowFormElementsOnHomePage() { $this->dispatch('/'); $this->assertXpath("//form//input[@name='fullname']"); $this->assertXpath("//form//input[@name='email']"); }
We should now get one failing test and to get this passing we change the index action yet again:
public function indexAction() { $form = new Zend_Form(); $form->addElement('text', 'fullname'); $form->addElement('text', 'email'); $this->view->form = $form; }
We once again get all our tests passing.
One point to note is that we haven’t had to go to the browser at all and once we can set setup the TDD (add test, verify failed test, add code to make test pass, verify passing test) rhythm, eliminating those browser refreshes should help to make us more efficient.
However we still need to look at the UI from time to time (especially when working on the view part of the MVC triad as we are now doing) and if we do that now we indeed see our form elements on the page. However we don’t have labels or a submit button and I’ll add those to the form now. The Index action now becomes:
public function indexAction() { $form = new Zend_Form(); $form->addElement('text', 'fullname', array( 'label' => 'Full Name' )); $form->addElement('text', 'email', array( 'label' => 'Email Address' )); $form->addElement('submit', 'submit', array( 'label' => 'Subscribe' )); $this->view->form = $form; }
We just added labels and a submit button without adding tests! Shouldn’t we be calling the TDD police?
In my opinion, page text doesn’t need testing. What would happen if we decided to support multiple languages or decided to change the ‘Full Name’ label to just ‘Name’? The form element names don’t have to change but if we added the labels to the test suite we’d have to change our tests anytime the labels changed. They are not essential to the application functionality so we can leave them out of the test suite.
Running our test suite should still give 4 passing tests so we know we haven’t broken anything.
We’ve completed the first requirement and it’s now time to take a break.
The next requirement we’ll tackle specifies that ‘As a user, I should be shown the form again when I submit invalid details’. How exactly do we test this? How does the view know the details are invalid? We’ll fix all that in the next section where we discuss mocks and stubs.
Related posts:
Pingback: Avnet Labs Blog: TDD with Zend Framework ‘” testing controllers | Webs Developer
This seems to be the common approach for testing controllers, but I can’t help notice it combines testing controllers and views together. It’s probably fine for most cases.
I wrote up a proposal for a test viewrenderer that does nothing but disables the view render: http://framework.zend.com/wiki/display/ZFPROP/Zend_Test_ViewRenderer
In that case, you could simply invoke the controller methods and assert against the view contents, rather than the representation …
$this->_controller->indexAction();
$this->assertNotNull($this->_controller->view->projects);
$this->assertEquals(26,count($this->_controller->view->projects));
Pingback: Avnet Labs Blog: TDD with Zend Framework - testing controllers | Webs Developer
Anyone know of a good pre built base of ZF 1.9 app with pre configured bootstrapper and tests I can download.
Hi, has anyone ever implemented a Model test using Zend_Test_PHPUnit_DataBaseTestCase class?
I’m having some problem with it, so if anyone can help.
Thanks in advance
Looking forward to the next awesome work!
@紅酒課程
I foud an useful article at :
http://weierophinney.net/matthew/archives/215-Rendering-Zend_Form-decorators-individually.html
@Redner
Try this:
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors#zfbook.implementing.the.domain.model.entries.and.authors.implementation.adding.unit.tests.for.execution
Yes,that book got me here !
Edit:
http://weierophinney.net/matthew/archives/230-Quick-Start-to-Zend_Application_Bootstrap.html
Pingback: blog.nielslange.de » Test Driven Development (TTD) with the Zend Framework
In my opionion you should add a test for the submit button, too. The label text doesn’t matter, but a check for the label existence (mapping the label’s “for” attribute to the input’s “name” attribute) would make sense in that case, wouldn’t it?