TDD with Zend Framework – wrapping up the controller

  • Part 1 – Getting Started
  • Part 2 – Testing Controllers
  • Part 3 – Mocks and Stubs
  • Part 4 – Wrapping up the controller
    • The last part of this series dealt with mocking and stubbing the controller action dependencies to get isolated unit tests. In this section we’ll round up the controller tests.

      • There is some repetition in the controller test and it’d be nice to clean that up.
      • Our subscribe action redirects to a thank you page. That doesn’t exist yet.
      • Our subscribe action expects a POST. We need to verify the behaviour if it receives a GET instead.

      When we left off, we had the following controller class:

      01<?php
      02 
      03class IndexController extends Zend_Controller_Action
      04{
      05 
      06    public function indexAction()
      07    {
      08        $form = Default_Form_SubscribeFactory::create();
      09        $this->view->form = $form;
      10    }
      11 
      12    public function subscribeAction()
      13    {
      14        $form = Default_Form_SubscribeFactory::create();
      15        if (!$form->isValid($this->_request->getPost())) {
      16           $this->view->form = $form;
      17           $this->render('index');
      18        } else {
      19            $subscriberModel = Default_Model_SubscriberFactory::create();
      20            $subscriberModel->save($form->getValues());
      21            $this->_redirect('index/thanks');
      22        }
      23    }
      24 
      25}

      and this corresponding test class:

      01<?php
      02 
      03require_once realpath(dirname(__FILE__) . '/../../TestHelper.php');
      04 
      05class IndexControllerTest extends BaseControllerTestCase
      06{
      07    public function testIndexAction() {
      08        $this->dispatch('/');
      09        $this->assertController('index');
      10        $this->assertAction('index');
      11    }
      12 
      13    public function testGetttingIndexActionShouldBeSuccessful() {
      14        $this->dispatch('/');
      15        $this->assertResponseCode(200);
      16        $this->assertNotRedirect();
      17    }
      18 
      19    public function testShouldShowFormOnHomePage() {
      20        $this->dispatch('/');
      21        $this->assertQuery('form[action*="subscribe"]');
      22        $this->assertQuery('form[id="subscribe-form"]');
      23    }
      24 
      25    public function testShouldShowFormElementsOnHomePage() {
      26        $this->dispatch('/');
      27        $this->assertXpath("//form//input[@name='fullname']");
      28        $this->assertXpath("//form//input[@name='email']");
      29    }
      30 
      31    public function testShouldRedisplayFormOnInvalidEntry() {
      32        $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
      33        $subscribeForm->expects($this->any())->method('isValid')
      34                       ->will($this->returnValue(false));
      35        Default_Form_SubscribeFactory::setForm($subscribeForm);
      36 
      37        $this->request->setMethod('POST');
      38        $this->dispatch('/index/subscribe');
      39        $this->assertNotRedirect();
      40        $this->assertQuery('form[id="subscribe-form"]');
      41    }
      42 
      43    public function testShouldRedirectToThankYouPageOnValidEntry() {
      44        $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
      45        $subscribeForm->expects($this->any())->method('isValid')
      46                       ->will($this->returnValue(true));
      47        Default_Form_SubscribeFactory::setForm($subscribeForm);
      48 
      49        $this->request->setMethod('POST');
      50        $this->dispatch('/index/subscribe');
      51        $this->assertRedirectTo('/index/thanks');
      52    }
      53 
      54    public function testShouldSaveDetailsOnValidEntry() {
      55        $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
      56        $subscribeForm->expects($this->any())->method('isValid')
      57                       ->will($this->returnValue(true));
      58        Default_Form_SubscribeFactory::setForm($subscribeForm);
      59 
      60        $subscriberModel = $this->getMock('Default_Model_Subscriber');
      61        $subscriberModel->expects($this->once())->method('save');
      62        Default_Model_SubscriberFactory::setModel($subscriberModel);
      63 
      64        $this->request->setMethod('POST');
      65        $this->dispatch('/index/subscribe');
      66    }
      67 
      68}

      First off, I’ll refactor the test case to reduce repetition. I’ll add two private utility methods – stubSubscribeForm and postSubscriberDetails – and change the relevant test cases to use these. These method names are descriptive and should enhance (rather than detract from) the readability of the test class. After the cleanup the test class becomes:

      01<?php
      02 
      03require_once realpath(dirname(__FILE__) . '/../../TestHelper.php');
      04 
      05class IndexControllerTest extends BaseControllerTestCase
      06{
      07    public function testIndexAction() {
      08        $this->dispatch('/');
      09        $this->assertController('index');
      10        $this->assertAction('index');
      11    }
      12 
      13    public function testGetttingIndexActionShouldBeSuccessful() {
      14        $this->dispatch('/');
      15        $this->assertResponseCode(200);
      16        $this->assertNotRedirect();
      17    }
      18 
      19    public function testShouldShowFormOnHomePage() {
      20        $this->dispatch('/');
      21        $this->assertQuery('form[action*="subscribe"]');
      22        $this->assertQuery('form[id="subscribe-form"]');
      23    }
      24 
      25    public function testShouldShowFormElementsOnHomePage() {
      26        $this->dispatch('/');
      27        $this->assertXpath("//form//input[@name='fullname']");
      28        $this->assertXpath("//form//input[@name='email']");
      29    }
      30 
      31    public function testShouldRedisplayFormOnInvalidEntry() {
      32        $this->_stubSubscribeForm(false);
      33 
      34        $this->_postSubscriberDetails();
      35        $this->assertNotRedirect();
      36        $this->assertQuery('form[id="subscribe-form"]');
      37    }
      38 
      39    public function testShouldRedirectToThankYouPageOnValidEntry() {
      40        $this->_stubSubscribeForm();
      41 
      42        $this->_postSubscriberDetails();
      43        $this->assertRedirectTo('/index/thanks');
      44    }
      45 
      46    public function testShouldSaveDetailsOnValidEntry() {
      47        $this->_stubSubscribeForm();
      48 
      49        $subscriberModel = $this->getMock('Default_Model_Subscriber');
      50        $subscriberModel->expects($this->once())->method('save');
      51        Default_Model_SubscriberFactory::setModel($subscriberModel);
      52 
      53        $this->_postSubscriberDetails();
      54    }
      55 
      56    private function _stubSubscribeForm($returnValue = true) {
      57        $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
      58        $subscribeForm->expects($this->any())->method('isValid')
      59                       ->will($this->returnValue($returnValue));
      60        Default_Form_SubscribeFactory::setForm($subscribeForm);
      61    }
      62 
      63    private function _postSubscriberDetails() {
      64        $this->request->setMethod('POST');
      65        $this->dispatch('/index/subscribe');
      66    }
      67 
      68}

      Our tests still pass. We’ll now add a test for the thank you page.

      1public function testGetttingThanksActionShouldBeSuccessful() {
      2    $this->dispatch('/index/thanks');
      3    $this->assertResponseCode(200);
      4    $this->assertNotRedirect();
      5}

      We get a failure when we run the test suite. We don’t have the thanks action yet. We’ll add the action and corresponding view file.

      1public function thanksAction()
      2{
      3}
      1// application/views/index/thanks.phtml
      2<h2>Thanks for subscribing</h2>
      3Thanks for subscribing to our site.

      Our test suite now passes.

      We do not want the subscribe action responding to any request not using an HTTP POST. We’ll add a test to verify we get redirected to the index action if we try to GET the action.

      1public function testGetttingSubscribeActionShouldRedirectToHomePage() {
      2    $this->request->setMethod('GET');
      3    $this->dispatch('/index/subscribe');
      4    $this->assertRedirectTo('/');
      5}

      We’ll get a failing test suite if we run it and to get it passing we’ll modify the subscribe action like thus:

      01public function subscribeAction()
      02{
      03    if (!$this->_request->isPost()) {
      04        $this->_redirect('/');
      05    } else {
      06        $form = Default_Form_SubscribeFactory::create();
      07        if (!$form->isValid($this->_request->getPost())) {
      08           $this->view->form = $form;
      09           $this->render('index');
      10        } else {
      11            $subscriberModel = Default_Model_SubscriberFactory::create();
      12            $subscriberModel->save($form->getValues());
      13            $this->_redirect('index/thanks');
      14        }
      15    }
      16}

      Once again, passing tests.

      Ideally we’d use a guard clause to filter out non-POST requests (and leave out the ‘else’ bit) but the Zend ControllerTestCase class sets up the redirector to not exit after redirecting and so any code after the redirection will still run. If it’s set to exit, the exit() call will terminate our tests so this makes sense. However, it also illustrates a potential downside to unit testing – sometimes it will influence the way we write our code and it’s not always for the better.

      Our controller is finally complete and we can now move on to other parts of our app. In the next section we’ll add validation to our subscribe form.

      Related posts:

      1. TDD with Zend Framework – mocks and stubs
      2. TDD with Zend Framework – testing controllers
      3. Restful Controllers with Zend Framework
      4. Zend Framework titbits
      5. TDD with Zend Framework
This entry was posted in Zend Framework. Bookmark the permalink.

2 Responses to TDD with Zend Framework – wrapping up the controller

  1. Pingback: blog.nielslange.de » Test Driven Development (TTD) with the Zend Framework

  2. 上海办证 says:

    每一份成功都是不容易的,就好像能来贵站访问,欢迎回访我们http://www.chengyibz.com每天都有新的内容。

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>