<< previous article: Disintegration Testing next article: PHPUnit 4.0: Code Coverage Improvements >>

PHPUnit 4.0: Test Proxies

Sebastian Bergmann | 03/12/2014 | Blog

One of the highlights of PHPUnit 4.0, which was released last week, is improved support for integration testing through so-called test proxies.

Background

Thanks to Gerard Meszaros, we have the following (names for) categories of test doubles:

PHPUnit has had built-in support for stubs and mocks for quite some time. These stubs and mocks can be used in every context where an object of the original class is expected. As it should be, the code of the original class is not executed when a method is called on the stub or mock.

Motivation for Test Proxies

              <?php
namespace company\project;

class SampleWorkflow
{
    private $backend;
    private $service;

    public function __construct(Backend $backend, Service $service)
    {
        $this->backend = $backend;
        $this->service = $service;
    }

    public function execute(Request $request)
    {
        // ...

        $this->service->doWork(
            $this->backend->getObjectById($request->getValue('id'))
        );

        // ...

        return new Result(/* ... */);
    }

    // ...
}
            

In an integration test you generally do not want to stub or mock dependencies. When we use instances of the real Backend, Service, and Request classes in a test for the execute() method of the SampleWorkflow class show above then we can only perform assertions on the method's return value. Whether or not the method returns the correct result does not, however, tell us anything about whether the collaboration with the Service object worked correctly, for instance.

Using a Test Proxy

PHPUnit 4.0 introduces the concept of test proxies to solve the problem described above. The idea is to have an object that provides the same API for expectations as a mock object while at the same time proxying method calls to the original class.

              <?php
namespace company\project;

use PHPUnit_Framework_TestCase;

class SampleWorkflowTest extends PHPUnit_Framework_TestCase
{
    private $backend;
    private $service;
    private $workflow;

    protected function setUp()
    {
        $this->backend = new Backend;

        $this->service = $this->getMockBuilder('Service')
                              ->enableProxyingToOriginalMethods()
                              ->getMock();

        $this->workflow = new SampleWorkflow(
            $this->backend,
            $this->service
        );
    }

    public function testExecutionTriggersWorkInService()
    {
        $this->service->expects($this->once())
                      ->method('doWork');

        $this->assertEquals(
            new Result(/* ... */),
            $workflow->execute(new Request(array('id' => 2204)))
        );
    }
}
            

In the test code above, we create a test proxy for the Service class. This is an object that can be used in every context where an object of the original class is expected. Using the fluent interface of PHPUnit's API for mock object we set up the expectation that the doWork() method is called exactly once on the test proxy. Unlike with a mock object, however, calling this method on the test proxy will execute the method's original code.

This example shows that we can not only verify the return value of a method such as execute() but also verify its interaction with its collaborating objects in an integration test.


Do you need help with test automation? Sebastian Bergmann, creator and project lead of PHPUnit, offers on-site trainings and workshops. He has over a decade of experience helping companies and Open Source projects around the world to leverage PHPUnit for unit tests, integration tests, and system tests.


<< previous article: Disintegration Testing next article: PHPUnit 4.0: Code Coverage Improvements >>