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:

      <?php
      
      class IndexController extends Zend_Controller_Action
      {
      
          public function indexAction()
          {
              $form = Default_Form_SubscribeFactory::create();
              $this->view->form = $form;
          }
      
          public function subscribeAction()
          {
              $form = Default_Form_SubscribeFactory::create();
              if (!$form->isValid($this->_request->getPost())) {
                 $this->view->form = $form;
                 $this->render('index');
              } else {
                  $subscriberModel = Default_Model_SubscriberFactory::create();
                  $subscriberModel->save($form->getValues());
                  $this->_redirect('index/thanks');
              }
          }
      
      }
      

      and this corresponding test class:

      <?php
      
      require_once realpath(dirname(__FILE__) . '/../../TestHelper.php');
      
      class IndexControllerTest extends BaseControllerTestCase
      {
          public function testIndexAction() {
              $this->dispatch('/');
              $this->assertController('index');
              $this->assertAction('index');
          }
      
          public function testGetttingIndexActionShouldBeSuccessful() {
              $this->dispatch('/');
              $this->assertResponseCode(200);
              $this->assertNotRedirect();
          }
      
          public function testShouldShowFormOnHomePage() {
          	$this->dispatch('/');
          	$this->assertQuery('form[action*="subscribe"]');
              $this->assertQuery('form[id="subscribe-form"]');
          }
      
          public function testShouldShowFormElementsOnHomePage() {
          	$this->dispatch('/');
          	$this->assertXpath("//form//input[@name='fullname']");
              $this->assertXpath("//form//input[@name='email']");
          }
      
          public function testShouldRedisplayFormOnInvalidEntry() {
              $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
              $subscribeForm->expects($this->any())->method('isValid')
                             ->will($this->returnValue(false));
              Default_Form_SubscribeFactory::setForm($subscribeForm);
      
          	$this->request->setMethod('POST');
          	$this->dispatch('/index/subscribe');
              $this->assertNotRedirect();
          	$this->assertQuery('form[id="subscribe-form"]');
          }
      
          public function testShouldRedirectToThankYouPageOnValidEntry() {
              $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
              $subscribeForm->expects($this->any())->method('isValid')
                             ->will($this->returnValue(true));
              Default_Form_SubscribeFactory::setForm($subscribeForm);
      
          	$this->request->setMethod('POST');
          	$this->dispatch('/index/subscribe');
              $this->assertRedirectTo('/index/thanks');
          }
      
          public function testShouldSaveDetailsOnValidEntry() {
              $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
              $subscribeForm->expects($this->any())->method('isValid')
                             ->will($this->returnValue(true));
              Default_Form_SubscribeFactory::setForm($subscribeForm);
      
              $subscriberModel = $this->getMock('Default_Model_Subscriber');
              $subscriberModel->expects($this->once())->method('save');
              Default_Model_SubscriberFactory::setModel($subscriberModel);
      
              $this->request->setMethod('POST');
          	$this->dispatch('/index/subscribe');
          }
      
      }
      

      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:

      <?php
      
      require_once realpath(dirname(__FILE__) . '/../../TestHelper.php');
      
      class IndexControllerTest extends BaseControllerTestCase
      {
          public function testIndexAction() {
              $this->dispatch('/');
              $this->assertController('index');
              $this->assertAction('index');
          }
      
          public function testGetttingIndexActionShouldBeSuccessful() {
              $this->dispatch('/');
              $this->assertResponseCode(200);
              $this->assertNotRedirect();
          }
      
          public function testShouldShowFormOnHomePage() {
          	$this->dispatch('/');
          	$this->assertQuery('form[action*="subscribe"]');
              $this->assertQuery('form[id="subscribe-form"]');
          }
      
          public function testShouldShowFormElementsOnHomePage() {
          	$this->dispatch('/');
          	$this->assertXpath("//form//input[@name='fullname']");
              $this->assertXpath("//form//input[@name='email']");
          }
      
          public function testShouldRedisplayFormOnInvalidEntry() {
              $this->_stubSubscribeForm(false);
      
          	$this->_postSubscriberDetails();
              $this->assertNotRedirect();
          	$this->assertQuery('form[id="subscribe-form"]');
          }
      
          public function testShouldRedirectToThankYouPageOnValidEntry() {
              $this->_stubSubscribeForm();
      
          	$this->_postSubscriberDetails();
              $this->assertRedirectTo('/index/thanks');
          }
      
          public function testShouldSaveDetailsOnValidEntry() {
              $this->_stubSubscribeForm();
      
              $subscriberModel = $this->getMock('Default_Model_Subscriber');
              $subscriberModel->expects($this->once())->method('save');
              Default_Model_SubscriberFactory::setModel($subscriberModel);
      
              $this->_postSubscriberDetails();
          }
      
          private function _stubSubscribeForm($returnValue = true) {
              $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
              $subscribeForm->expects($this->any())->method('isValid')
                             ->will($this->returnValue($returnValue));
              Default_Form_SubscribeFactory::setForm($subscribeForm);
          }
      
          private function _postSubscriberDetails() {
              $this->request->setMethod('POST');
          	$this->dispatch('/index/subscribe');
          }
      
      }
      

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

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

      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.

          public function thanksAction()
          {
          }
      
      // application/views/index/thanks.phtml
      <h2>Thanks for subscribing</h2>
      Thanks 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.

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

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

          public function subscribeAction()
          {
              if (!$this->_request->isPost()) {
                  $this->_redirect('/');
              } else {
                  $form = Default_Form_SubscribeFactory::create();
                  if (!$form->isValid($this->_request->getPost())) {
                     $this->view->form = $form;
                     $this->render('index');
                  } else {
                      $subscriberModel = Default_Model_SubscriberFactory::create();
                      $subscriberModel->save($form->getValues());
                      $this->_redirect('index/thanks');
                  }
              }
          }
      

      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.

Posted in Zend Framework | 2 Comments

TDD with Zend Framework – mocks and stubs

  • Part 1 – Getting Started
  • Part 2 – Testing Controllers
  • Part 3 – Mocks and Stubs
  • Part 4 – Wrapping up the controller
    • In the last part we tested for the form elements on the home page to meet the first of our requirements. In this part we’ll take on a few more requirements starting with the requirement that the form should be redisplayed when I submit invalid details.
      For the details to be invalid it means we have validated the data and it failed validation. However our validation code is not done yet but we still need to ensure the controller and view do what we require when the details are invalid.

      Zend_Form straddles both the view and model when it comes to MVC and seperation of concerns. We have used the view part to display the elements. To validate data we use the model part of Zend_Form and the isValid() method to check the validation status. The controller need not know what makes data valid and so to test the controller behaviour with valid and invalid data we have to simulate a form with passing validation and one with failing validation. This is where mocks and stubs come in. They are actually interchangeable (most of the time) but I’ll highlight the differences later on.

      The first step to simulating Zend_Form validation results is to extract the form from the controller action into its own class. I’ll create a Subscribe.php file in the forms folder and the module autoloader should then autoload it.

      <?php
      // application/forms/Subscribe.php
      
      class Default_Form_Subscribe extends Zend_Form {
      
          public function init() {
              $this->setAttrib('id', 'subscribe-form');
              $this->addElement('text', 'fullname', array(
                  'label' => 'Full Name'
              ));
              $this->addElement('text', 'email', array(
                  'label' => 'Email Address'
              ));
              $this->addElement('submit', 'submit', array(
                 'label' => 'Subscribe'
              ));
          }
      
      }
      

      The controller index action then becomes:

      public function indexAction()
      {
          $form = new Default_Form_Subscribe();
          $this->view->form = $form;
      }
      

      We should still get passing tests when we run the test suite.
      This highlights one very important advantage TDD gives us. We can refactor code with confidence without breaking functionality as we’ve got the tests in place to watch our backs.

      However even with this change, we still cannot simulate the form class. To do that we have to inject our own version of the form class with our preferred output from the isValid() method. We are still creating an instance of the class in the action method and the only way we can replace the form instance is to create it elsewhere.
      Here, the factory pattern comes to our rescue – rather than create the object where we need it, we can get an external form factory class to create it and fetch it from the index action. We’ll first create a test for the factory class to act as a guide.

      <?php
      // tests/application/forms/SubscribeFormFactoryTest.php
      
      require_once realpath(dirname(__FILE__) . '/../../TestHelper.php');
      
      class SubscribeFormFactoryTest extends PHPUnit_Framework_TestCase
      {
          public function testShouldCreateForm() {
              $form = Default_Form_SubscribeFactory::create();
              $this->assertNotNull($form);
              $this->assertType('Default_Form_Subscribe', $form);
          }
      }
      

      This class extends the PHPUnit_Framework_TestCase rather than the BaseControllerTestCase as we don’t need all the controller stuff. I have also defined a static create method which should return an instance of our form.
      We get a ‘Fatal Error ‘ when we try running the test suite as the class and method don’t exist. I’ll add them now:

      <?php
      
      class Default_Form_SubscribeFactory {
      
          public static function create() {
              return new Default_Form_Subscribe();
          }
      
      }
      

      We once again get passing tests when we run our test suite. We now need to be able to inject a form into the factory class and when we call the create method we get our form instance rather than the default one. I’ll add a test for that:

          public function testShouldSetCustomFormInstance() {
              $mockForm = $this->getMock('Default_Form_Subscribe');
              Default_Form_SubscribeFactory::setForm($mockForm);
              $form = Default_Form_SubscribeFactory::create();
              $this->assertEquals($mockForm, $form);
          }
      

      PHPUnit provides a getMock() method to set up mock objects. Mock Objects are simulated objects that mimic the behavior of real objects in controlled ways [wikipedia] and are useful in unit tests when it’s impossible or not practical to use the real object. For a primer in using mocks with unit tests check out this post on CodeUtopia.
      For our test, we create a mock form object, inject it into our form factory and assert we get our custom instance back when we call the create() method. We don’t have an implementation so our test suite should fail when we run it.
      I’ll add the following implementation to get the tests to pass.

      
      <?php
      
      class Default_Form_SubscribeFactory {
      
          private static $_form = null;
      
          public static function create() {
              if (is_null(self::$_form)) {
                  self::$_form = new Default_Form_Subscribe();
              }
              return self::$_form;
          }
      
          public static function setForm($form) {
              self::$_form = $form;
          }
      
      }
      

      Once again we get passing tests and our subscribe form factory is complete.
      To use our form factory in the Index controller I’ll change the index action to:

      <?php
      
      class IndexController extends Zend_Controller_Action
      {
          public function indexAction()
          {
              $form = Default_Form_SubscribeFactory::create();
              $this->view->form = $form;
          }
      }
      
      Creating a factory class for each form would get old really quickly if we have a lot of forms. To counter this we could use a generic form factory class which would lazy-load any form in the application. View an example on github.
      An alternative is to use a Dependency Injection framework to load any controller dependencies. Two popular ones in the ZF community are Yadif and Symfony Dependency Injection

      We still get passing tests when we run our test suite and we can replace the form instance if we need to.
      We can now add our test to verify the behaviour when the details are invalid.

      public function testShouldRedisplayFormOnInvalidEntry() {
              $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
              $subscribeForm->expects($this->any())->method('isValid')
                             ->will($this->returnValue(false));
              Default_Form_SubscribeFactory::setForm($subscribeForm);
      
          	$this->request->setMethod('POST');
          	$this->dispatch('/index/subscribe');
              $this->assertNotRedirect();
          	$this->assertQuery('form[id="subscribe-form"]');
      }
      

      Here I am creating a mock form object and stubbing the isValid method to return false (simulating invalid details). Using our form factory, we then inject our version of the form into the controller. The second parameter of the getMock method is optional and specifies the methods we need to mock. If left blank, all methods will be mocked but in our case we don’t want that as we later assert the form elements are displayed. The request method is changed to a post and I assert that there is no redirect and that the form is redisplayed.

      This test fails! We are dispatching to an action we haven’t created and so that’s expected. We’ll add the subscribe action now.

          public function subscribeAction()
          {
              $form = Default_Form_SubscribeFactory::create();
              if (!$form->isValid($this->_request->getPost())) {
                 $this->view->form = $form;
                 $this->render('index');
              }
          }
      

      Our tests now pass. Our app is redisplaying the form when the details are invalid. However what should happen when we send valid details. From our specs, two things should happen:

      • I should be redirected to a thank you page.
      • The submitted details should be saved to the database.

      We’ll write a test for the first action.

        public function testShouldRedirectToThankYouPageOnValidEntry() {
              $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
              $subscribeForm->expects($this->any())->method('isValid')
                             ->will($this->returnValue(true));
              Default_Form_SubscribeFactory::setForm($subscribeForm);
      
          	$this->request->setMethod('POST');
          	$this->dispatch('/index/subscribe');
              $this->assertRedirectTo('/index/thanks');
          }
      

      Our tests fail again and we change the subscribe action to the version below to get them passing again.

          public function subscribeAction()
          {
              $form = Default_Form_SubscribeFactory::create();
              if (!$form->isValid($this->_request->getPost())) {
                 $this->view->form = $form;
                 $this->render('index');
              } else {
                  $this->_redirect('index/thanks');
              }
          }
      

      Let’s now write a test to verify the details get saved to the database. This is another tricky one. It’s not the controller’s job to save the details to the database and so it has to call a method in a model class to do that. However, we don’t have the model class written yet so we’ll have to come up with an appropriate method signature. This illustrates how TDD helps us flesh out our application’s API.

      Once again, we don’t really care how the model gets the job done and so we’ll mock out the model and add a model factory like we did with the form. I’ll fast forward this section and just show the implementation (it’s basically the same as the form – although we will revisit it when we need to add dependencies).

      <?php
      // tests/application/models/SubscriberModelFactoryTest.php
      
      require_once realpath(dirname(__FILE__) . '/../../TestHelper.php');
      
      class SubscriberModelFactoryTest extends PHPUnit_Framework_TestCase
      {
          public function testShouldCreateModel() {
              $model = Default_Model_SubscriberFactory::create();
              $this->assertNotNull($model);
              $this->assertType('Default_Model_Subscriber', $model);
          }
      
          public function testShouldSetCustomModelInstance() {
              $mockModel = $this->getMock('Default_Model_Subscriber');
              Default_Model_SubscriberFactory::setModel($mockModel);
              $model = Default_Model_SubscriberFactory::create();
              $this->assertEquals($mockModel, $model);
          }
      
      }
      
      <?php
      // application/models/SubscriberFactory.php
      
      class Default_Model_SubscriberFactory {
      
          private static $_model = null;
      
          public static function create() {
              if (is_null(self::$_model)) {
                  self::$_model = new Default_Model_Subscriber();
              }
              return self::$_model;
          }
      
          public static function setModel($model) {
              self::$_model = $model;
          }
      
      }
      

      Here again, in the real world, we’d use an abstract factory class or even better, a dependency injection framework but this will have to do for now.
      Running our test suite at this point gives us an error telling us it can’t find the ‘Default_Model_Subscriber’ class. We’ll have to create it to move on.

      <?php
      // application/models/Subscriber.php
      
      class Default_Model_Subscriber
      {
      
      }
      

      We should now get passing tests.

      We’ll now add a test for to verify the details are saved. We’ll assume the ‘Subscriber’ model class has a method call ‘save’ which saves the data to the database.

          public function testShouldSaveDetailsOnValidEntry() {
              $subscribeForm = $this->getMock('Default_Form_Subscribe', array('isValid'));
              $subscribeForm->expects($this->any())->method('isValid')
                             ->will($this->returnValue(true));
              Default_Form_SubscribeFactory::setForm($subscribeForm);
      
              $subscriberModel = $this->getMock('Default_Model_Subscriber');
              $subscriberModel->expects($this->once())->method('save');
              Default_Model_SubscriberFactory::setModel($subscriberModel);
      
              $this->request->setMethod('POST');
          	$this->dispatch('/index/subscribe');
          }
      

      Running the test suite gives us the following error:

      Expectation failed for method name is equal to <string:save> when invoked 1 time(s).
      Method was expected to be called 1 times, actually called 0 times.
      

      And there lies the difference between mocks and stubs. Our previous usage of the PHPunit mock object have actually been stubs. With a stub, we are verifying state (we return a known response from a method call and verify the state of our app) while with a mock we are verifying behaviour (we expect the save method to be called). With the mock object we don’t even have to assert anything. The expectations will fail if they are not fulfilled.

      To get our tests to pass we’ll add the call to the save method.

          public function subscribeAction()
          {
              $form = Default_Form_SubscribeFactory::create();
              if (!$form->isValid($this->_request->getPost())) {
                 $this->view->form = $form;
                 $this->render('index');
              } else {
                  $subscriberModel = Default_Model_SubscriberFactory::create();
                  $subscriberModel->save($form->getValues());
                  $this->_redirect('index/thanks');
              }
          }
      

      We get an error when we run the test suite – one of our other test cases is choking on the call to the save method that doesn’t exist. To fix that we’ll add an empty implementation.

      <?php
      // application/models/Subscriber.php
      
      class Default_Model_Subscriber
      {
      
          public function save($data) {
      
          }
      
      }
      

      Yay! Passing tests.

      What Next?

      We’re almost done with the controller. We need to add the thanks page and add a typical edge case verification – I’m assuming the subscribe action will always receive a POST. What happens if I GET the action?
      There’s also a lot of repetition in our IndexControllerTest class while setting up our mocks. We’ll need to DRY this up.

      We’ll take care of all this in the next section.

      Resources

Posted in Zend Framework | 1 Comment

Zend Framework 1.8 Web Application Development – book review

I’ve just finished going through Zend Framework 1.8 Web Application Development, a new book by Keith Pope. Published by Packt Publishing this book which somehow manages to cater to the ZF beginner as well as the veteran developers is a must-read for anyone doing Zend Framework development.

First impressions are “this guy sure knows his stuff”.
Subsequent impressions are “how the &^%$ did this guy know all this stuff?”.
The first three chapters are aimed at ZF newbies but even at that I still found a lot of nuggets I had managed to overlook even after working with the Zend Framework for almost 2 years. The first two chapters kick off with a run-through of a Zend Framework application and the ZF architecture while the third chapter introduces an online store app which forms the basis for the rest of the book.

The fourth chapter (which I kept going back to) tackles models in the Zend Framework – arguably the most controversial component (or non-component) in the framework and I really like how the author managed to convince me to see his point of view even though I’ve got really strong views about how my model should be structured. He also does offer alternatives although in the end he still goes the domain-driven route.

The rest of the book walks through building the storefront application but special mention must go to the chapter on authentication and authorization. For the first time I found a reason to take a second look at Zend_Acl – the access control component in ZF. Thanks to the book, I’m actually considering ditching my hand-rolled version for Zend_Acl. In my view, that’s quite an achievement. The book rounds off with chapters on optimization and unit testing – two often-neglected parts of php development.

On the whole I’m rather impressed even though I disagree with some of the author’s ways (like using Ant, for instance) and I wholeheartedly recommend this book.

Posted in Zend Framework | 6 Comments

TDD with Zend Framework – testing controllers

  • 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.

Posted in Zend Framework | 9 Comments

TDD with Zend Framework

  • Part 1 – Getting Started
  • Part 2 – Testing Controllers
  • Part 3 – Mocks and Stubs
  • Part 4 – Wrapping up the controller
    • Jani Hartikainen has an interesting unit testing series on his blog where he introduces and expands on unit testing php code with phpunit. It’s a really good read and it pushed me to add his feed to my reader so I could stop going to the site daily to check for updates.

      However, as useful as the series is, it doesn’t provide answers to Zend Framework specific testing issues (and considering all the ZF-specific posts on his site, that’s quite surprising). It is also aimed at beginner programmers. Most of the programmers I work with don’t need to know what a unit test or an assertion is. They need to know how to test ZF controllers and models and I was sort of hoping I could just point them at the series.

      In the coming weeks, I’ll put together a how-to on testing ZF applications using a Test Driven Development (TDD) approach. With TDD tests are written first and as such the test suite guides the design of the production code. Uncle Bob has a rather intriguing article where he summarises TDD into three ‘simple’ rules:

      • No production unless there’s a failing test
      • Write no more of a unit test than is sufficient to fail
      • Write no more production code than is sufficient to pass the failing test

      In this series, I will attempt to build a mini-application using TDD and the Zend Framework.

      The mini app

      I have chosen to build a newsletter subscription form as a test app. It’s simple enough to not distract from the primary aim of the exercise while providing a cause to test the entire MVC stack as well as the sending of emails.

      The mini-app will have relatively simple requirements:

      • 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 user, I should be shown the form to try submitting my details again when I submit invalid details.

        • The fullname field is required, should be at least 2 characters long and contain only alphanumeric characters and spaces.
        • The email field is required and should be a valid email address.
      • As a user, I should be redirected to a thank you page when I submit valid details.

        • The submitted (and validated) details should be saved to the database.
      • As a user, I should get a confirmation email to complete my subscription.

      Setting up

      I am going to assume a fresh Zend Framework 1.9 application with the test files already setup. I found this Zend Framework 1.8 with Zend_Application and PHPUnit walk-through quite helpful.
      Alternatively, download a copy of Jara Base either from GitHub or as a zipped file.

      I will also assume some level of comfort with the Zend Framework. If not, the afore-mention Quick Start and Akra’s ZF 1.9 tutorial should come in handy.
      I will be using the trial version of Zend Studio 7 for this so I can test it out (need to know if it’s worth upgrading). I have this set up on my laptop running windows XP not my regular dev box (running Ubuntu) and I had the following issues while setting up PHPunit and Zend Framework on Windows. Hopefully my fixes will help someone.

      • I got a ‘Cannot redeclare class Zend_Openid_Provider’ when trying to run Zend_Tool. I had to remove a duplicate copy of the Zend Framework library.
      • I also got ‘Warning (Phpunit_Framework_Warning) No tests found in file …’. Short tags were disabled in CLI php.ini file and the file had short tags in it.
      • Php was not in my path. Had to add it.
      • I had old version of pear(1.5.4) and to upgrade, I had to uninstall it first.
      • Installing mysgit gave me a nice bash console on windows.

      I also tested this out on a fresh ubuntu install (9.04) and got the following error while trying to install PHPUnit:

      Did not download optional dependencies: pear/Image_GraphViz, pear/Log, use --alldeps to download automatically
      phpunit/PHPUnit requires PEAR Installer (version >= 1.8.1), installed version is 1.7.1
      phpunit/PHPUnit can optionally use package "pear/Image_GraphViz" (version >= 1.2.1)
      phpunit/PHPUnit can optionally use package "pear/Log"
      phpunit/PHPUnit can optionally use PHP extension "xdebug" (version >= 2.0.0)
      No valid packages found
      install failed
      

      To fix that, upgrade pear with: sudo pear upgrade-all and the error goes away.

      Getting started

      Jara Base already has the unit testing framework setup and includes tests for the Home page and about pages so I’ll just use that.
      Zend Framework has no set way of setting tests up so I’ve used what I’m most comfortable with – which is a blend of the setup in the ZendCasts unit testing tutorial, the Ruby on Rails test setup and the test setup in the Quick Start example application.
      My setup includes a TestHelper file and the first part of this file (as shown below) is almost a copy of the application index.php file. The differences are that the APPLICATION_ENV constant is set to ‘test’ and Zend Application isn’t included.
      The next bit is an abstract BaseControllerTestCase class and all controller tests should extend this class. Only the controller tests need Zend_Application and so the bootstraping is done in there.

      <?php
      set_include_path(implode(PATH_SEPARATOR, array(
          realpath(dirname(__FILE__) . '/../library'),
          get_include_path(),
      )));
      
      define('APPLICATION_ENV', 'test');
      defined('APPLICATION_PATH')
          || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));
      
      require_once 'Zend/Loader/Autoloader.php';
      $autoloader = Zend_Loader_Autoloader::getInstance();
      
      /**
       * Base Controller Test Class
       *
       * All controller test should extend this
       */
      abstract class BaseControllerTestCase extends Zend_Test_PHPUnit_ControllerTestCase {
      
          public function setUp() {
              $application = new Zend_Application(APPLICATION_ENV, APPLICATION_PATH . '/configs/application.ini');
              $this->bootstrap = array($application->getBootstrap(), 'bootstrap');
              return parent::setUp();
          }
      
          public function tearDown() {
              /* Tear Down Routine */
          }
      }
      

      To confirm everything’s setup properly I’ll open up a console and navigate to the tests folder in the application root.
      Running the ‘phpunit’ command should give me passing tests (using Jara Base). To follow along, the tests can be deleted so we start with a blank slate.

      There are two main school of thoughts regarding where to start when it comes to TDD and web apps – domain logic first or interface first.
      They both have their pros and cons but in my opinion starting the models first encourages functional testing and not unit testing (in the real sense of it) since the the models are already working by the time you get to testing the controllers.

      I will go the interface first route and the next part starts off with testing controllers (and bits of the view).

Posted in Zend Framework | 12 Comments

Bullshit of the day

From a Virgin Media call center agent before trying to get me to upgrade my package at a ‘discount’:

“Your name has been added to the list of our valued customers.”

To me this seems to imply

  • I hadn’t been valued all the time I’d been with them.
  • They still have customers who pay them money every month who aren’t valued.

I’m sure she didn’t like my answer. I expected more from Virgin.

Posted in General | Leave a comment

Restful Controllers with Zend Framework

I just read Bradley Holt’s post on using HTML 5 forms with REST and HTTP methods and he mentioned the REST router in the recently released version 1.9 of the Zend Framework. The good news is we don’t have to wait for mainstream support for HTML 5.
Zend Framework REST controller supports PUT and DELETE HTTP methods right now through overloading the POST method (ok, it’s a bit of hack – but it’s worked in Rails for ages). Although the ZF 1.9 announcement mentions using Zend_Rest_Route for public APIs, building any application (or parts of it) restfully has a couple of advantages:

  • Consistency and Convention
    REST implies that the application is broken up into resources and these resources are manipulated using the HTTP interface methods (GET, POST, PUT and DELETE). If each controller corresponds to a resource, the controller methods stay consistent throughout the app and that’s a good thing.
  • Clarity
    Using the restful actions force a limit on the number of controller methods preventing the controllers from becoming bloated. This makes them both easier to read and debug.
  • Safety
    A typical restful controller has just two url endpoints eg. a books controller typically has ‘books’ and ‘books/:id’ as endpoints. Non destructive actions (listing collections and listing a single resource) are triggered on a GET request and the destructive actions get called on the other requests (or just POST if method overloading is used). This prevents non-idempotent actions from using a GET request so mistakes like putting a delete button behind a get request are impossible.
    It also removes the need for HTTP method checking in the code so
    if ($this->_request->isPost()) isn’t needed.
  • Reuse
    When a public API is eventually needed the changes will be minimal.

The code snippet below is for an example restful BooksController extending the Zend_Rest_Controller. The Zend_Rest_Controller stubs out 5 abstract methods but if all the methods aren’t needed, the code still works if Zend_Controller_Action is extended.
The summary is that two resource endpoints are used:

  • ‘/books’ for a collection – a GET request lists the collection and a POST request adds to the collection)
  • ‘/books/:id’ for a single resource – a GET request lists the details for a single resource, a PUT request updates the resource and a DELETE request deletes the resource)

I have also added two helper methods to the controller – a newAction method to render the new book form and an editAction method to render the edit book form.

You can grab the full demo on github.

class BooksController extends Zend_Rest_Controller
{

	private $_booksTable;
	private $_form;

    public function init()
    {
        $bootstrap = $this->getInvokeArg('bootstrap');
        $db = $bootstrap->getResource('db');

		$options = $bootstrap->getOption('resources');
		$dbFile  = $options['db']['params']['dbname'];
		if (!file_exists($dbFile)) {
		    $createTable = "CREATE TABLE IF NOT EXISTS books (
    					id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    					name VARCHAR(32) NOT NULL,
					    price DECIMAL(5,2) NOT NULL
					)";
			$db->query($createTable);

			$insert1 = "INSERT INTO books (name, price) VALUES ('jQuery in Action', 39.99)";
			$insert2 = "INSERT INTO books (name, price) VALUES ('PHP in Action', 45.99)";
			$db->query($insert1);
	        $db->query($insert2);
		}

		$this->_booksTable = new Zend_Db_Table('books');
		$this->_form = new Default_Form_Book();
    }

    /**
     * The index action handles index/list requests; it should respond with a
     * list of the requested resources.
     */
    public function indexAction()
    {
        $this->view->books = $this->_booksTable->fetchAll();
    }

 	/**
     * The list action is the default for the rest controller
     * Forward to index
     */
    public function listAction()
    {
        $this->_forward('index');
    }

    /**
     * The get action handles GET requests and receives an 'id' parameter; it
     * should respond with the server resource state of the resource identified
     * by the 'id' value.
     */
    public function getAction()
    {
    	$this->view->book = $this->_booksTable->find($this->_getParam('id'))->current();
    }

	/**
     * Show the new book form
     */
    public function newAction() {
    	$this->view->form = $this->_form;
    }

    /**
     * The post action handles POST requests; it should accept and digest a
     * POSTed resource representation and persist the resource state.
     */
    public function postAction() {
    	if ($this->_form->isValid($this->_request->getParams())) {
    		$this->_booksTable->createRow($this->_form->getValues())->save();
       		$this->_redirect('books');
    	} else {
    		$this->view->form = $this->_form;
    		$this->render('new');
    	}
    }

 	/**
     * Show the edit book form. Url format: /books/edit/2
     */
    public function editAction() {
    	$book = $this->_booksTable->find($this->_getParam('edit'))->current();
    	$this->_form->populate($book->toArray());
    	$this->view->form = $this->_form;
    	$this->view->book = $book;
    }

    /**
     * The put action handles PUT requests and receives an 'id' parameter; it
     * should update the server resource state of the resource identified by
     * the 'id' value.
     */
    public function putAction() {
    	$book = $this->_booksTable->find($this->_getParam('id'))->current();
    	if ($this->_form->isValid($this->_request->getParams())) {
    		$book->setFromArray($this->_form->getValues())->save();
       		$this->_redirect('books');
    	} else {
    		$this->view->book = $book;
    		$this->view->form = $this->_form;
    		$this->render('edit');
    	}
    }

    /**
     * The delete action handles DELETE requests and receives an 'id'
     * parameter; it should update the server resource state of the resource
     * identified by the 'id' value.
     */
    public function deleteAction() {
    	$book = $this->_booksTable->find($this->_getParam('id'))->current();
    	$book->delete();
    	$this->_redirect('books');
    }

}

To get this working the following resource methods need to be added to the Bootstrap file.

    protected function _initRestRoute()
	{
		$this->bootstrap('Request');
		$front = $this->getResource('FrontController');
		$restRoute = new Zend_Rest_Route($front, array(), array(
		    'default' => array('books')
		));
		$front->getRouter()->addRoute('rest', $restRoute);
	}

    protected function _initRequest()
    {
        $this->bootstrap('FrontController');
        $front = $this->getResource('FrontController');
        $request = $front->getRequest();
    	if (null === $front->getRequest()) {
            $request = new Zend_Controller_Request_Http();
            $front->setRequest($request);
        }
    	return $request;
    }

Restful controllers do add some much-needed consistency to Zend framework apps and hopefully we’ll start seeing them widely used real soon.

Posted in Zend Framework | 2 Comments

Zend_Db_Table update in ZF1.9

I usually have a bunch of empty Db_Table classes in my Zend Framework apps which just exist to define table names and do the inherited CRUD stuff.

However, with the just released version 1.9 of the framework (currently in beta), I don’t need to do this anymore. Zend_Db_Table is now a concrete implementation that accepts a table name as a parameter and can do all the CRUD stuff without needing a class to be defined.

Obviously, if custom code needs to be added to the Db_Table instance a class needs to be created but this will still go a long way towards cleaning up my models folder.

As an example:

  • The old way
    class Books extends Zend_Db_Table_Abstract {
    	protected $_name = 'books';
    }
    
    // and in the controller (or service layer class in my case)
    $booksTable = new Books();
    $books = $booksTable ->fetchAll();
    
  • The new way
    // no Db_Table subclass needed
    $booksTable = new Zend_Db_Table('books');
    $books = $booksTable ->fetchAll();
    

Another addition to Zend_Db is Zend_Db_Table_Definition and the concrete Zend_Db_Table constructor accepts an instance of this as a second parameter.
All setup code that can be added to an instance of Zend_Db_Table_Abstract (e.g. relationships) can be added through a definition class but the value of this is lost on me as this could lead to the same piece of code being defined in multiple places. In my opinion if the instance requires more than a table name, create a class for it.

Posted in Zend Framework | Leave a comment