Unit Testing with Zend 2 and PHPUnit

In this article we'll be taking a look at how to integrate PHPUnit with Zend 2 and how to run some simple tests against services and controllers. In a continuation of this article we'll also look at how you can structure your application to improve the testing process. 

To start with though, let's get everyone on the same page with an introduction to unit testing...

What is Unit Testing?

Unit testing is the concept of breaking down your application into small units of code that can individually have consise tests run against them. As each unit test is written as part of the application, you can re-run the tests any time a modification is made, which will automatically flag up any unexpected knock on affects. 

Benefits of Unit Testing

Unit testing can add a lot of overhead to a project, especially in Test Driven Development (TDD), so why should you do it?

  • Better coding practice
    By writing your code so that it is testable, requires you to write code that isn't tightly coupled.
  • Documentation
    Unit tests serve as a form of documentation for other developers to see what functionality is provided by the unit of code.
  • Code confidence
    If you've got good code coverage with unit tests that you can run at any time, then you can be more confident in the robustness of the code. This helps you make modifications to the application with out the fear of what it might break.
  • Find bugs early on
    As you'll write and run tests alongside development, any problems will be picked up as soon as they occur, rather than at the end of the development process when having to backtrack may be costly. 

Drawbacks & Limitations

Unit testing is designed to test that the individual components that make up an application all continue to work individually. What it is not designed to do is pick up integration errors whereby there are bugs occurring from how the different units of code are interacting (though, you'll inevitably find that some of your unit tests do overlap with integration tests). 

One of the biggest drawbacks of unit testing is that it adds a massive overhead to the development process. Arguably however, you'll save that time later when you don't have to spend so much time debugging code!

Terminology

I'm not going to regurgitate the whole phpunit documentation to you as you can read that for yourself, but let's quickly go over a few of the core concepts that are referenced later on:

  • Assertions
    These are what you add to your tests to 'assert' that something is true, e.g. an AssertEquals would be used to check that a returned value matches what is expected. Typically for best practice you would have one assertion per test though there's nothing to stop you having several.
  • Stubs
    If the unit of code you're testing has a dependency on another object, you could use a 'stub' as a 'test double' to replace the real component. This allows you to focus on only testing that one unit of code.  You can configure stub methods to return custom values.
  • Mocks
    A mock is similar to a stub in that it is also a 'test double', but is used to verify that the test double object has received requests from the subject code in accordance with defined expectations. 
  • Test Driven Development (TDD)
    I'll save the details of this for another day, but TDD is a common technique used to write your tests before you write your code. If you're not familiar with this it might sound like an odd concept but it is widely considered to lead to better coding practice.

The terms 'mock' and 'stub' are often confused and interchangably used, there's also 'dummies' and 'fakes' to throw into the mix to add to the confusion. Martin Fowler writes a good explanation on the differences.

Pre-requisites

Before we move onto integration between PHPUnit and Zend 2, here's the pre-requisites I'm assuming as a starting point...

Zend 2 Installation

Zend Framework is set up, if you're using Doctrine for your ORM too there's a useful installation guide by Paul Underwood , otherwise refer to the Zend Documenation.

Install PHPUnit

Check the PHPUnit Documentation for steps on how to install it, if you're using composer you can also pull it in as a dependency by adding the following to your composer.json file:


"require-dev": {
        "phpunit/phpunit" : "4.1.*"
    }

Test PHPUnit is correctly installed by typing 'PHPUnit' at a command prompt, you should see the help options available. Zend 2 requires PHPUnit version 3.7 or later.

Configuration

Create a 'tests' folder in the root of your application.

Inside this folder create the following 'phpunit.xml' file:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="Bootstrap.php">
    <testsuites>
        <testsuite name="cs">
            <directory>../module/</directory>
        </testsuite>
   </testsuites>
</phpunit>

This informs PHPUnit to look for test files anywhere inside your 'module' directory and to use the bootstrap file to get things set up when PHPUnit runs.

Next we create the bootstrap file (code based off an article posted on dev blog).

<?php
namespace ApplicationTest;

use Zend\Mvc;
use Zend\ServiceManager\ServiceManager;
use Zend\Mvc\Service\ServiceManagerConfig;

class bootstrap
{
    static $serviceManager;

    static function go()
    {
        // Make everything relative to the root
        chdir(dirname(__DIR__));

        // Setup autoloading
        require_once( __DIR__ . '/../init_autoloader.php' );

        // Run application
        $config = require('config/application.config.php');
        \Zend\Mvc\Application::init($config);

        $serviceManager = new ServiceManager(new ServiceManagerConfig());
        $serviceManager->setService('ApplicationConfig', $config);
        $serviceManager->get('ModuleManager')->loadModules();

        self::$serviceManager = $serviceManager;
    }

    static public function getServiceManager()
    {
        return self::$serviceManager;
    }
}

bootstrap::go();

This code sets up Zend using the same configuration file, and provides a method for retrieving the Service Manager. 

Create a service / class test

Add a simple service

To demonstrate how this works, I'm going to add a simple 'user service' (a class that will have a doSomething method), which will be part of the default 'Application' module. In a real-world application this class might be used to send and retrieve user information from a database.

To add the service to your application module, create a new directory inside 'src' > 'Application' titled 'Service' (so this is at the same level as the 'Controller' directory). 

Inside the new directory add a file titled 'UserService.php' with the following code:


namespace Application\Service;

class UserService
{
    public function doSomething()
    {
        return "hello world!";
    }
}

Nothing too complicated there. Next we need to register this service within our module so that the Service Manager is aware it exists. To do this we create the 'getServiceConfig' method inside the application 'module.php' file:


public function getServiceConfig()
{
    return array(
        'factories' => array(
            'UserService' =>  function($sm)
            {
                return new \Application\Service\UserService($sm);
            }
        ),
    );
}

Note that 'UserService' is the reference that can now be used to retrieve an instance of this class. So with our new service registered, we could access it inside a controller action like so:


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

Now let's write a simple test to check that 'hello world!' actually gets returned by this service.

Create a test

Tests should belong in the individual module directories mimicking the structure of the src directory - here's an example for testing the index controller and user service (don't worry about the controller test for now, we'll come to that shortly):

  • module
    • Application
      • src
        • Application
          • Controller
            •  IndexController.php
          • Service
            • UserService.php
      • test
        • ApplicationTest
          • Controller
            • IndexControllerTest.php
          • Service
            • UserServiceTest.php

Use the code below for the UserServiceTest.php file. 

<?php
namespace ApplicationTest\Service;
use ApplicationManagement\Service;
use ApplicationTest\Bootstrap;
use PHPUnit_Framework_TestCase;

class UserTest extends \PHPUnit_Framework_TestCase
{
    protected $serviceManager;
    protected $userService;

    protected function setUp()
    {
        $this->serviceManager = Bootstrap::getServiceManager();
        $this->userService = $this->serviceManager->get('UserService');
    }

    public function testHelloWorld()
    {
        $response = $this->userService->doSomething();
        $this->assertEquals('hello world!', $response);
    }
}

Above we retrieve an instance of the user service in the setup for this test, then in our 'testHelloWorld' method we assert that the response matches what we're expecting. 

Now load up a command prompt, switch to the tests folder and run phpunit:

cd \application-path\tests
phpunit

All the tests found in your modules folder will now be run. All being well you'll see the following output (assuming no other tests currently exist):

OK (1 test, 1 assertions)

A simple controller test

Depending on who you speak to you're likely to get a different response regarding the testing of controllers. In my opinion, the controllers only really bring together all the other elements of the application that should already have unit tests (e.g. most of the business logic will be in other classes loaded in by the Service Manager). For this reason, there isn't really much to test in a controller except that the action requests load, and return the right response (usually the view model). With this in mind, I'd argue that it's still worth doing controller tests, but more as an 'integration' test rather that a 'unit' test.

For the purists though, and playing devils advocate on my above statement, I'll show you below how to do 'proper' controller tests that are only testing controller code in the next section using Dependency Injection.

Before we get to that though, let's start with a simple controller test (I'm assuming you already have an index controller in your application module).

The following code for IndexControllerTest.php sets everything up in the setUp function and then does a simple test that tries to dispatch the index action, which expects an OK 200 response:


<?php 

namespace ApplicationTest\Controller;

use Zend\Test\PHPUnit\Controller;
use ApplicationTest\Bootstrap;
use Zend\Mvc\Router\Http\TreeRouteStack as HttpRouter;
use Application\Controller\IndexController;
use Zend\Http\Request;
use Zend\Http\Response;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Router\RouteMatch;
use PHPUnit_Framework_TestCase;

class IndexControllerTest extends \PHPUnit_Framework_TestCase
{
    protected $controller;
    protected $request;
    protected $response;
    protected $routeMatch;
    protected $event;

    protected function setUp()
    {
        $serviceManager = Bootstrap::getServiceManager();
        $this->controller = new IndexController();
        $this->request    = new Request();
        $this->routeMatch = new RouteMatch(array('controller' => 'index'));
        $this->event      = new MvcEvent();
        $config = $serviceManager->get('Config');
        $routerConfig = isset($config['router']) ? $config['router'] : array();
        $router = HttpRouter::factory($routerConfig);

        $this->event->setRouter($router);
        $this->event->setRouteMatch($this->routeMatch);
        $this->controller->setEvent($this->event);
        $this->controller->setServiceLocator($serviceManager);
    }

    public function testIndexActionCanBeAccessed()
    {
        $this->routeMatch->setParam('action', 'index');

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

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

Now load up a command prompt, switch to the tests folder and run phpunit:

cd \application-path\tests
phpunit

All the tests found in your modules folder will now be run. If there is anything wrong with your index controller or action you may see the following error:

Failed asserting that 404 matches expected 200.

..otherwise you would see the following:

OK (1 test, 1 assertions)

So you now have PHPUnit configured with Zend 2 and have an example of unit testing with services and controllers. If you're interesting in improving your application configuration for better controller tests, continue on to the next article on testing controllers.

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.