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:
03 | class IndexController extends Zend_Controller_Action |
06 | public function indexAction() |
08 | $form = Default_Form_SubscribeFactory::create(); |
09 | $this ->view->form = $form ; |
12 | public function subscribeAction() |
14 | $form = Default_Form_SubscribeFactory::create(); |
15 | if (! $form ->isValid( $this ->_request->getPost())) { |
16 | $this ->view->form = $form ; |
17 | $this ->render( 'index' ); |
19 | $subscriberModel = Default_Model_SubscriberFactory::create(); |
20 | $subscriberModel ->save( $form ->getValues()); |
21 | $this ->_redirect( 'index/thanks' ); |
and this corresponding test class:
03 | require_once realpath (dirname( __FILE__ ) . '/../../TestHelper.php' ); |
05 | class IndexControllerTest extends BaseControllerTestCase |
07 | public function testIndexAction() { |
09 | $this ->assertController( 'index' ); |
10 | $this ->assertAction( 'index' ); |
13 | public function testGetttingIndexActionShouldBeSuccessful() { |
15 | $this ->assertResponseCode(200); |
16 | $this ->assertNotRedirect(); |
19 | public function testShouldShowFormOnHomePage() { |
21 | $this ->assertQuery( 'form[action*="subscribe"]' ); |
22 | $this ->assertQuery( 'form[id="subscribe-form"]' ); |
25 | public function testShouldShowFormElementsOnHomePage() { |
27 | $this ->assertXpath( "//form//input[@name='fullname']" ); |
28 | $this ->assertXpath( "//form//input[@name='email']" ); |
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 ); |
37 | $this ->request->setMethod( 'POST' ); |
38 | $this ->dispatch( '/index/subscribe' ); |
39 | $this ->assertNotRedirect(); |
40 | $this ->assertQuery( 'form[id="subscribe-form"]' ); |
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 ); |
49 | $this ->request->setMethod( 'POST' ); |
50 | $this ->dispatch( '/index/subscribe' ); |
51 | $this ->assertRedirectTo( '/index/thanks' ); |
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 ); |
60 | $subscriberModel = $this ->getMock( 'Default_Model_Subscriber' ); |
61 | $subscriberModel ->expects( $this ->once())->method( 'save' ); |
62 | Default_Model_SubscriberFactory::setModel( $subscriberModel ); |
64 | $this ->request->setMethod( 'POST' ); |
65 | $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:
03 | require_once realpath (dirname( __FILE__ ) . '/../../TestHelper.php' ); |
05 | class IndexControllerTest extends BaseControllerTestCase |
07 | public function testIndexAction() { |
09 | $this ->assertController( 'index' ); |
10 | $this ->assertAction( 'index' ); |
13 | public function testGetttingIndexActionShouldBeSuccessful() { |
15 | $this ->assertResponseCode(200); |
16 | $this ->assertNotRedirect(); |
19 | public function testShouldShowFormOnHomePage() { |
21 | $this ->assertQuery( 'form[action*="subscribe"]' ); |
22 | $this ->assertQuery( 'form[id="subscribe-form"]' ); |
25 | public function testShouldShowFormElementsOnHomePage() { |
27 | $this ->assertXpath( "//form//input[@name='fullname']" ); |
28 | $this ->assertXpath( "//form//input[@name='email']" ); |
31 | public function testShouldRedisplayFormOnInvalidEntry() { |
32 | $this ->_stubSubscribeForm(false); |
34 | $this ->_postSubscriberDetails(); |
35 | $this ->assertNotRedirect(); |
36 | $this ->assertQuery( 'form[id="subscribe-form"]' ); |
39 | public function testShouldRedirectToThankYouPageOnValidEntry() { |
40 | $this ->_stubSubscribeForm(); |
42 | $this ->_postSubscriberDetails(); |
43 | $this ->assertRedirectTo( '/index/thanks' ); |
46 | public function testShouldSaveDetailsOnValidEntry() { |
47 | $this ->_stubSubscribeForm(); |
49 | $subscriberModel = $this ->getMock( 'Default_Model_Subscriber' ); |
50 | $subscriberModel ->expects( $this ->once())->method( 'save' ); |
51 | Default_Model_SubscriberFactory::setModel( $subscriberModel ); |
53 | $this ->_postSubscriberDetails(); |
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 ); |
63 | private function _postSubscriberDetails() { |
64 | $this ->request->setMethod( 'POST' ); |
65 | $this ->dispatch( '/index/subscribe' ); |
Our tests still pass. We’ll now add a test for the thank you page.
1 | public function testGetttingThanksActionShouldBeSuccessful() { |
2 | $this ->dispatch( '/index/thanks' ); |
3 | $this ->assertResponseCode(200); |
4 | $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.
1 | public function thanksAction() |
2 | <h2>Thanks for subscribing</h2> |
3 | 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.
1 | public function testGetttingSubscribeActionShouldRedirectToHomePage() { |
2 | $this ->request->setMethod( 'GET' ); |
3 | $this ->dispatch( '/index/subscribe' ); |
4 | $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:
01 | public function subscribeAction() |
03 | if (! $this ->_request->isPost()) { |
04 | $this ->_redirect( '/' ); |
06 | $form = Default_Form_SubscribeFactory::create(); |
07 | if (! $form ->isValid( $this ->_request->getPost())) { |
08 | $this ->view->form = $form ; |
09 | $this ->render( 'index' ); |
11 | $subscriberModel = Default_Model_SubscriberFactory::create(); |
12 | $subscriberModel ->save( $form ->getValues()); |
13 | $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.
Related posts:
- TDD with Zend Framework – mocks and stubs
- TDD with Zend Framework – testing controllers
- Restful Controllers with Zend Framework
- Zend Framework titbits
- TDD with Zend Framework
Pingback: blog.nielslange.de » Test Driven Development (TTD) with the Zend Framework
每一份成功都是不容易的,就好像能来贵站访问,欢迎回访我们http://www.chengyibz.com每天都有新的内容。