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

Practical PHP Testing Patterns: Test Stub

DZone 's Guide to

Practical PHP Testing Patterns: Test Stub

· Web Dev Zone ·
Free Resource

The problem of testing in isolation is verifying only logic contained in the System Under Test, and not also of the various composed objects at the same time. For example, we may want to verify that our view helper prints a nice form, without having to really inject in it the web service or the database access object which it obtains the <select> options from.

Thus we have to replace the real object, the collaborator of our SUT, with a Test Double. In this article, we will consider a subset of Test Doubles named Test Stubs: they are objects which return predefined results to the SUT when called. As for all Test Double, they are totally equivalent to the real objects they substitute for the duration of the current test. However, Test Doubles provide us control on what the SUT sees.

When you have to replace input for testing purposes, it's simple and you can do also in procedural (structured) programming. When you have to replace a function that is called instead, object-oriented programming is really helpful (you substitute the single object.)

For the ones of you which have some knowledge in electronics, a Stub can be seen as a stateless combinatorial network, which provides a predefined output to a set of inputs. This is true also for the real object which the Stub replaces; however the Stub only responds to a small subset of input configurations (the ones needed during the test).

Implementation

There are several simple steps to follow for building a Stub.

1. Produce an alternate implementation of the contract required by your SUT: another implementation of the interface of the collaborator, or a subclass of its concrete/abstract class if you do not want to extract an interface.

2. Hard-code or configure its parameters (what it returns in response to what combination of parameters).

3. Install the Stub into the SUT by injection or configuration.

4. Exercise the SUT by providing inputs that will only need the values you have set up in the Stub to be returned.

Note that the Stub only returns its predefined output, and it makes no verification on what is called with. Many times you can build the Stub automatically with PHPUnit's getMock(), getMockBuilder() and getMockForAbstractClass(). PHPUnit uses the term Mock for almost all Test Doubles.

Variations

  • Responder: a simple Stub which returns valid data, but which are not really subject of the test. This is the simple variation; for example if the view you're testing always needs to obtain the title and description of the page to avoid blowing up, when you're testing something else you can define a Stub which returns empty strings for title and description.
  • Saboteur: returns invalid data when called by the SUT; its purpose is seeing how the SUT reacts and manage the error. The advantage of wrapping web services or a database in a collaborator is that you can substitute it with a Stub and provoke errors in the testing environment, even if the real service is doing fine at the time of execution.
  • Temporary Test Stub: a Stub that is written before the real implementation. When following outside in (Acceptance) Test-Driven Development this variation is really common. If you find difficult writing the Stub, you can immediately revise the contract between it and the SUT, before having the burden of a real class to adapt.
  • Procedural Test Stub: procedural Stub realized with if (TEST) in the production code. Never follow this road.
  • Entity Chain Snipping: replacement of an object graph with a single Facade. If you follow the Law of Demeter, writing Stubs and particularly this variation will be much simpler.

Hard-Coded vs Configurable

Hard-Coded Test Stubs contain results valid only for a handful of tests, as they cannot change. They are usually written by hand.

Configurable Test Stub can also be written by hand, but their result can be configured for the current test and more often they are generated on the fly.
Note that resorting to generation only because it is impossible to hand-roll a Stub usually means that the design is too complex, or tied to concrete classes because of missing abstractions. However if you generate only for expressiveness and speed, you have my kudos.

Example

The code sample expands on the previous article's one, which already presented a Stub. Here you see instead how to generate one with PHPUnit, starting from an interface or the concrete class you want to substitute.

<?php
class TestStubTest extends PHPUnit_Framework_TestCase
{
    /**
     * This is the same Test Stub of the previous article.
     * It is hand rolled, but not Hard-Coded as we can pass the data in the 
     * constructor.
     */
    public function testCalculatesAverageVisitorsNumber()
    {
        $source = new StubDataSource(array(40000, 50000, 100000, 20000, 40000));
        $statistics = new Statistics($source);
        $this->assertEquals(50000, $statistics->getAverage());
    }

    /**
     * You can easily generate Stub with PHPUnit.
     */
    public function testCalculatesAverageVisitorsNumberWithGeneratedStub()
    {
        $source = $this->getMock('DataSource');
        $source->expects($this->any())
               ->method('getSamples')
               ->will($this->returnValue((array(40000, 50000, 100000, 20000, 40000))));
        $statistics = new Statistics($source);
        $this->assertEquals(50000, $statistics->getAverage());
    }

    /**
     * Sometimes you do not have an interface or abstract class to Stub out, 
     * but only the real implementation. The best thing to do here would be to
     * extract an interface with only the method actually called by the SUT.
     * However maybe you have ten Singletons to remove before lunch, and you 
     * just want to cover this class as it is not your priority to refactor it.
     * So you can provide a Stub for the concrete class for the time being.
     * In this case, we use my MockBuilder to override the original constructor
     * of the collaborator and avoid passing in collaborators' collaborators
     * in a neverending chain.
     */
    public function testCalculatesAverageVisitorsNumberWithGeneratedStubForTheConcreteClass()
    {
        $source = $this->getMockBuilder('GoogleAnalyticsDataSource')
                       ->disableOriginalConstructor()
                       ->getMock();
        $source->expects($this->any())
               ->method('getSamples')
               ->will($this->returnValue((array(40000, 50000, 100000, 20000, 40000))));
        $statistics = new Statistics($source);
        $this->assertEquals(50000, $statistics->getAverage());
    }
}

/**
 * The System Under Test (same as previous article).
 * It requires a DataSource collaborator to be used in production,
 * or to be tested.
 */
class Statistics
{
    private $source;

    public function __construct(DataSource $source)
    {
        $this->source = $source;
    }

    public function getAverage()
    {
        $samples = $this->source->getSamples();
        return array_sum($samples) / count($samples);
    }
}

/**
 * This is the contract of the source, the collaborator for the SUT.
 * It's not mandatory to have an explicit interface, particularly in PHP,
 * but it helps.
 */
interface DataSource
{
    /**
     * @return array    numerical values of visitors to this website
     */
    public function getSamples();
}

/**
 * The real implementation, this time with an obnoxious constructor.
 */
class GoogleAnalyticsDataSource implements DataSource
{
    public function __construct(SplQueue $queue, SomeOtherObject $object)
    {
        // ...
    }

    public function getSamples()
    {
        $result = curl("...");
        return $result;
    }
}

/**
 * An hard-coded Test Stub which is simply configured with 
 * predefined results. Even if the real web service is down or your
 * network does not work, this Stub allows the unit test to be executed.
 * It's also much faster than a bunch of network calls: in a single test you
 * may not notice them, but with a test suite of hundreths of tests...
 */
class StubDataSource implements DataSource
{
    private $samples;

    public function __construct(array $samples)
    {
        $this->samples = $samples;
    }

    public function getSamples()
    {
        return $this->samples;
    }
}
Topics:

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}