I had come to the conclusion that what I needed to do was create an instance of my controller in an environment where it I could get it to run each action I wanted to test. I started stepping through the code and worked out that the MVC Application "dispatches" the controller which works out from the Router & Request which action it should run which it "execute" via an MvcEvent.

I spent a bit of time trying to work this out but then I decided to reference the test suite for Zend Framework 2 itself and sure enough I found that the ActionControllerTest provided a great demo of testing an ActionController class. This test can be found in the Zend Framework 2 files under:

Shell

1

tests/Zend/Mvc/Controller/ActionControllerTest.php

After looking at this test I "borrowed" the following set up:

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

<?php

useAlbum\Controller\AlbumController,

Zend\Http\Request,

Zend\Http\Response,

Zend\Mvc\MvcEvent,

Zend\Mvc\Router\RouteMatch;

classAlbumControllerTestextendsPHPUnit_Framework_TestCase

{

protected$controller;

protected$request;

protected$response;

protected$routeMatch;

protected$event;

publicfunctionsetUp()

{

$this->controller=newAlbumController();

$this->request=newRequest();

$this->routeMatch=newRouteMatch(array('controller'=>'album'));

$this->event=newMvcEvent();

$this->event->setRouteMatch($this->routeMatch);

$this->controller->setEvent($this->event);

}

// Tests go here

}

In setUp() we’ve created an instance of the Controller we want to test, got a Request object the pass in when we dispatch() the controller, a RouteMatch which we can use to specify what action to execute and an MvcEvent which contains the RouteMatch object and is assigned to the controller.

Now this is done we can write some tests. First up I made sure I had a simple action to test by adding the following method to AlbumController,

PHP

1

2

3

4

publicfunctiontestAction()

{

returnnewViewModel(array("var"=>"test"));

}

then created a test for it.

In the test the first thing we have to do is tell the RouteMatch which action it should execute, the simply dispatch() the controller.

Once the dispatch() returns we can fetch the Response from the controller to check what status it will send back to the browser and check the result from the dispatch(), this is whatever was returned from the action:

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

publicfunctiontestTestAction()

{

// Specify which action to run

$this->routeMatch->setParam('action','test');

// Kick the controller into action

$result=$this->controller->dispatch($this->request);

// Check the HTTP response code

$response=$this->controller->getResponse();

$this->assertEquals(200,$response->getStatusCode());

// Check for a ViewModel to be returned

$this->assertInstanceOf('Zend\View\Model\ViewModel',$result);

// Test the parameters contained in the View model

$vars=$result->getVariables();

$this->assertTrue(isset($vars['var']));

}

I also added a little test for if an invalid action was dispatched, this was just an opportunity to try testings against a different status code.

PHP

1

2

3

4

5

6

7

publicfunctiontest404()

{

$this->routeMatch->setParam('action','action-that-doesnt-exist');

$result=$this->controller->dispatch($this->request);

$response=$this->controller->getResponse();

$this->assertEquals(404,$response->getStatusCode());

}

So that’s it, testing a ZF2 Mvc ActionController! I didn’t however manage to test my other actions as these refer to the ServiceManager which isn’t available in the environment that I’ve constructed, I have however been pointed to an example of using the ServiceManager in tests by the wonderful Rob Allen who so far is my favourite person in ZF2 development (so much so that he’s earned the place of being the first blog I’ve linked to in my blogroll) so I will hopefully get that up and running in my next post!

Tiago, the reason you are getting that error is because the controller has all sorts of dependencies that are not visible to you as they are hidden in the abstract class you have extend.

This is the same problem Tom has ran into. He said at the end of his article that he was not able to write certain tests because his controller uses some functionality that requires access the service manager. Even though Tom might not access the service manager directly in his action method his controller may well do (directly or indirectly) via methods inherited from the abstract class.

Instead of trying to think up a way to get a hold of these dependencies that the controller needs (because of inherited functionality from the framework) lets first consider what we are trying to achieve in unit testing our action methods.

The purpose of testing our action methods is to ensure that the code WE have written does what it is supposed to do. If we look at the ‘testAction’ method above we can see it does nothing accept return a view model object. So why is the unit test for that method concerned with Request, RouteMatch, MvcEvent objects and the frameworks dispatch process? This is framework code and we are not developing framework code so it shouldn’t be part of these tests.

The unit test for Toms ‘testAction’ should do the following:
1. Invoke AlbumController::testAction() directly.
After all, this is what happens outside of test when code is invoked. Even though the framework code is doing lots of other things before invoking this method, this shouldn’t be a concern of the application developer.
2. Assert that the return value is an instance of ViewModel.
3. Assert that ViewModel::getVariables() returns the correct data.

As you can imagine this makes for a much more clean and readable test case. You are testing only the code that you wrote since that is the only code you need to verify does what you expect.

In the AlbumController::test404() action you are not testing any of your code. You are testing functionality that is from the framework so I would argue that this test is redundant. Leave it to the framework developers to verify that their code works.

Now Tiago said he wants to test a method that makes use of the redirect plug-in but his test fails with an exception because that plug-in has a dependency that isn’t available in the test environment. Again the answer is to test only the code that you have written in your test action and nothing more. Since your action makes use of the redirect plug-in you will need to replace that plug-in instance with a mock object. Unfortunately the framework developers haven’t made it easy to test code that uses plug-ins but it is possible.

Here are some options:
1. Create an instance of Zend\Mvc\Controller\PluginManager and inject a mock of the plugin you want to use into it before injecting the PluginManager into your controller.
Though this would work I think this is not a good option because it means we have to run framework code in the test (PluginManager) and we have already said that our test should be testing only the code we have written not framework code.

2. Create a new class that extends Zend\Mvc\Controller\AbstractActionController and create a setPlugin($name, $pluginInstance) method which sets plugin instances to an array member variable. Override the plugin($name, $options) method to check if a plugin with the given name exists in your plugins array. If it does then return that otherwise invoke parent::plugin($name, $options) and return the result. Now all of your controllers should extend this new controller. This allows you to set the mock plugin directly to your controller in your test case without any need to deal with Zend\Mvc\Controller\PluginManager. Its not pretty but its better than the alternatives.

3. Petition the framework developers to make controller testing easier. The current controller dispatching implementation definitely has a bad smell about it. IMHO the framework should make it easy for application developers to isolate themselves from framework code when testing. In an ideal world controllers would not be required to extend any class or implement any interface and the different options for invoking controller actions would be handled one level above. Maybe some sort of dispatching strategy pattern. Ideally dependencies such as plugins should be injected. If not by default then it should at least be an option.