Testing controllers in Zend 2 with PHP Unit

Re-cap...

This article is a continuation of 'Unit Testing with Zend 2 and PHPUnit'. In this article we'll be looking at how to set up your controllers in your Zend 2 application for better unit testing by using dependency injection to load in the services that the controller requires. For simple controller tests please refer back to the initial post

Testing Controllers with Dependency Injection

So, in the last article we demonstrated how a simple controller test can be achieved, but things get a bit trickier when the controller is referencing other objects. Consider this - if the controller references a specific service which it retrieves via the service manager inside an action, then you can't unit test your controller action without all the service code being written and functioning correctly. You're also in a situation where your unit test is no longer testing just the controller, but also all the supporting code. This goes against the fundamental principle of a unit test - testing one thing at a time.

So how do we solve this?

Queue dependency injection. It's been around for a long time but has become a bit of a buzz word in PHP in recent years for driving forward the structure and patterns that are used in larger PHP applications. Essentially, it is the concept of passing through (injecting) objects (dependencies) into the constructor of a class that the class is dependent on. What's the benefit of doing this over just fetching the dependency within the code? From a testing perspective it allows you to 'mock' or 'stub' the dependencies that are passed in to a class, allowing your test to focus on only testing the current piece of code.

Setting up controller dependency injection in Zend

So how do we set up dependency injection for controllers in Zend? This can all be achieved in a module's configuration files - let's step through the process...

So remember the service we setup earlier could be accessed in a controller like so:


$userService = $this->getServiceLocator()->get('UserService');
$userService->doSomething();

But we don't want to have to retrieve it directly in the code - we want to dependency inject it to avoid coupling. 

Dependency inject the service

In Zend2, controllers that you use within modules are set up as 'invokables' within the module.config.php file. To dependency inject the controllers we're going to need to do this slightly differently, so to start with, remove the invokable for the index controller from the module.config.php file inside your application module:

'controllers' => array(
    'invokables' => array(
       # 'Application\Controller\Index' => 'Application\Controller\IndexController',
    ),
),

Note that for this to work I'm assuming that if you were to browse to the root of your application it would default to the IndexController and index action, if not you can use the following or adapt the rest of the code accordingly (the default skeleton application uses segment routing, this is fine):

'router' => array(
    'routes' => array(
        'home' => array(
            'type'    => 'Literal',
            'options' => array(
                'route'    => '/',
                'defaults' => array(
                    '__NAMESPACE__' => 'Application\Controller',
                    'controller'    => 'Index',
                    'action'        => 'index'
                ),
            ),
        )
     ),

So, currently we don't have a controller registered, so let's go ahead and register that in the application module.php file:

public function getControllerConfig()
{
    return array(
        'factories' => array(
            'Application\Controller\Index' => function ($sm)
            {
                $locator = $sm->getServiceLocator();
                $userService = $locator->get('UserService');

                $controller = new \Application\Controller\IndexController($userService);
                return $controller;
            },
        ),
    );
}

The above provides the application with the configuration options for this particular controller. Within the factory we're saying that if routing occurs to 'Application\Controller\Index' then get the 'UserService' we created, inject it into a new instance of the controller class and then return it. 

Lastly, in the controller we need to receive this new service:

public function __construct( $userService )
{
    $userService->doSomething();
}

I've also run the test function in the construct just to demonstrate it works. In practice, you would assign the $userService to a protected variable to be used by the actions or constructor. 

And that's it! You've now got dependency injection working for your controller making it much easier for... testing.

Testing your controller with a stubbed service

So to finish off then, let's prove that all that configuration was worth while by testing our controller with a stubbed service. Going back to our original IndexControllerTest.php file we can now update the setUp method to stub the UserService which we pass into the IndexController:


protected function setUp()
{
    $userService = $this->getMockBuilder('Application\Service\UserService')
        ->disableOriginalConstructor()
        ->setMethods(array('test'))
        ->getMock();
    $userService->method('test')
                ->willReturn('hello world!');


    $serviceManager = Bootstrap::getServiceManager();
    $this->controller = new IndexController($userService);
    //...

This will create a stub object of the UserService, then we define what we want returned from the 'test' method. Now in this simple test we've basically just replicated the UserService as the stub does the same thing, but the benefit here is that if the 'test' method didn't actually exist because we hadn't built it yet, we can just fake it until we get round to writing and testing that bit of code independently later. Also, if the UserService did more complex tasks (like write to the database), the stub wouldn't, avoiding the overhead for testing purposes.

Note the 'setMethods' function called against getMockBuilder - this seems to be underdocumented but is required for the test method to be overridden.

And that's it, re-run your tests and you can now independently check that the controller code is working without having to also test the service. 

Summary

We've covered quite a lot in the last two articles - you should now be setup with Unit Testing in Zend, have created your first test and have an understanding of how dependency injection can be useful for structuring your application in a way that makes controllers easily testable. 

Sign Up
comments powered by Disqus
Sign Up

Popular Tags

350x250

Need a web developer?

If you'd like to work with code synthesis on your next project get in touch via the contact page.