Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical PHP Testing Patterns: Test Helper

DZone 's Guide to

Practical PHP Testing Patterns: Test Helper

· Web Dev Zone ·
Free Resource

Duplicated test code should be factored out in a single place, like a Test Utility Method, to avoid rotting and difficulties in its maintenance. Yet sharing methods between Testcase Classes is not trivial.

We have seen a way for sharing method that involves inheritance: the Testcase Superclass pattern. However, there is a very powerful alternative to it which uses composition instead: the Test Helper pattern. A Test Helper is an external class, which does not need to extend PHPUnit base classes, which holds Test Utility Methods free to be used in any other test case after instantiation. The main advantage of a composition-based approach is that you have N blocks (your helpers) that you can compose at will, including or leaving out some of them in each test case.

For example, we initially discovered this pattern for testing CRUD controllers which vary in operations supported: one helper for create operations, one for edit ones, one for deletions, one for reading. Given a controller, you can compose and configure from 0 to 4 helpers, and choose which end-to-end tests automatically perform on it; sometimes you write the tests yourself instead of picking the helper, or don't write them because the operation is not supported (e.g. deleting clients or invoices), omitting the helper.

Implementation

I really prefer instances to static classes, as you probably know. However, you'll never mock out these kinds of objects.

In general, Testcase Classes instance one or more test helper to do their job; usually in the setUp() method. An helper can even hold entire tests: in that case the Testcase Class will define the relevant test*() methods and delegate to it. If the helper needs configuration, it can takes parameters in the constructor. You are free to define the Api as the only requirement for helpers is that their classes must be autoloadable.

An important constructor parameter is usually $this (an instance of PHPUnit_Framework_TestCase), a reference that the helper can store and call for obtaining mocks, matchers and make assertions. It's common that you factor out code in Test Helpers: by injecting $this you can continue to use primitives like $this->any() or $this->assertTrue() without shaking up too much the existing code.

The visibility of the methods called on the Testcase Class by Test Helpers must be of course public, but this is not an issue from PHPUnit 3.5; you may however explicitly set some of your Testcase Class methods as private to hide them from helpers. However, your own private or protected methods become public when extracted on an helper.

Variations

A Test Fixture Registry is a Test Helper that accesses the same fixtures as other tests, like a shared database connection. It's handy when there are many resources like this and a single Testcase Superclass for managing all of them won't cut it.

An Object Mother is a Builder or a Factory for objects used in tests. It produces domain objects with an Api friendly to test code, in order for test to stay readable; it's commonly used across many test classes. In our variant, it may even insert these objects in the database, for functional testing purposes.

Example

The code sample shows you a test case class before and after the introduction of the pattern.

Before the Test Helper extraction, duplicated code is a private method that must be inherited by subclassing.

<?php
class TestHelperBeforeTest extends PHPUnit_Framework_TestCase
{
    /**
     * Our only Test Method, but there could be others.
     */
    public function testArrayRespectsStandardStructure()
    {
        $array = array(
            'code' => 200,
            'content' => '...'
        );
        $this->assertArrayRespectsStandardStructure($array);
    }

    /**
     * This is the method we want to extract: a Custom Assertion.
     * We suppose this code is duplicated in other classes, which are won't 
     * included here for brevity.
     */
    private function assertArrayRespectsStandardStructure(array $array)
    {
        $this->assertTrue(isset($array['code']), 'Missing "code" key.');
        $this->assertTrue(is_numeric($array['code']), '"code" key invalid.');
        $this->assertTrue(isset($array['content']), 'Missing "content" key.');
    }
}

After the pattern's application, this code is in an helper that you can use from anywhere, without inheritance; especially if you already use inheritance to gain a reference to some other utility methods.

<?php
class TestHelperAfterTest extends PHPUnit_Framework_TestCase
{
    /**
     * We create the Test Helper in the setUp() in case other Test Methods
     * that need it are present.
     */
    public function setUp()
    {
        $this->standardsHelper = new StandardsHelper($this);
    }

    public function testArrayRespectsStandardStructure()
    {
        $array = array(
            'code' => 200,
            'content' => '...'
        );
        $this->standardsHelper->assertArrayRespectsStandardStructure($array);
    }
}

class StandardsHelper
{
    /**
     * Composing the Testcase Object is useful because of the access to assert*() functions,
     * but also to getMock(), any() and many other utility methods. They could be reimplemented,
     * but compositions is really much less work.
     * Incidentally, this also demonstrates why it's correct to keep methods private 
     * on the Testcase class: they are preserved from usage "da parte di" Test Helpers.
     */
    public function __construct(PHPUnit_Framework_TestCase $testCase)
    {
        $this->testCase = $testCase;
    }

    /**
     * The Custom Assertion becomes public. Other methods which are only called inside
     * this class can remain private.
     */
    public function assertArrayRespectsStandardStructure(array $array)
    {
        $this->testCase->assertTrue(isset($array['code']), 'Missing "code" key.');
        $this->testCase->assertTrue(is_numeric($array['code']), '"code" key invalid.');
        $this->testCase->assertTrue(isset($array['content']), 'Missing "content" key.');
    }
}
Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}