Agile Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 638 posts at DZone. You can read more from them at their website. View Full User Profile

Practical PHP Testing Patterns: Dummy Object

06.08.2011
| 6109 views |
  • submit to reddit

A Dummy Object is the simplest specialization of the Test Double pattern. As always, if you want to avoid constructing the real collaborator of the System Under Test, use a Test Double: you'll get a shorter and simpler test, with the plus of isolation from other concrete classes.

When no methods are called on a Test Double, nor fields are accessed, the Dummy Object is the ideal specialization. Usually this kind of object is only passed around to satisfy type hints and null checks. Also checks may be made on its equality or identicalness in the internals of other Test Doubles (Mocks or Stubs) where it is passed as a parameter or returned.

Thus a really short definition of a Dummy Object is a Test Double whose only utility is being passed around as a method parameter (this comprehends also constructor parameters that must be filled in.) Note also that Dummy values can be used in PHP, where not everything is an object. You may substitute collaborators even with strings.

Implementation

The easiest way to implement a Dummy Object is to pass null instead of the real object. If there is a type hint, this method requires an = null clause that sets null as the default value (this is different from Java semantics where you can always pass null if there are no explicit checks.)

I use this trick on constructors sometimes: they are only called in a Factory anyway, so making their parameters optional is not dangeours. But on methods it's not feasible to add defaults as we like, as it disfigures the Api. Moreover, you can't do equality checks on null: all null values are the same (just like electrons).

Languages even more dynamic than PHP remove the possibility of type hints (which mimics static types) and allow everything to be passed in, as long as you don't call a not existent method. In PHP, it's not really an hint: you must pass an instance of that type for the method to avoid explosion.

A simple subclass (or interface implementation) can do the job, and you can define it in the same source file as the test, in order to instantiate an object inline. In this case, namespaces (as manual prefixes or the PHP 5.3 ones) may help in avoiding conflicts.

A final possibility is of leveraging PHPUnit support with getMock(). For PHPUnit everything is a mock: however, this method can generate also Dummy Objects.
In case you're substituting concrete classes, overriding the constructor may be necessary: in this case, my getMockBuilder() Api may help you.

One advantage of PHPUnit magic generation is that it's possible to add expectations on Dummy Objects, and test that an method is not invoked by raising an error in case of a call. The $this->never() matcher serves this purpose, but you should set it on all methods. It may be an superfluos check, but if the production code really calls a method on a Dummy built in this way, null will be returned silently.

Variations

  • A Dummy Argument is provided as a method parameter during a call, either directly in the test code or indirectly in the production code which we exercise.
  • A Dummy Attribute instead is an attribute (aka field) populated with a Test Double to avoid complex construction. For the purpose of the current test, the contents of these fields may be ignored: for example, in the exercised methods there is no mention of them.

Example

The sample code shows you how to create a Dummy Object in every possible way I know.

<?php
class DummyObjectTest extends PHPUnit_Framework_TestCase
{
    /**
     * The Test Method names describe the different ways to produce
     * a Dummy Object (or just a Dummy value).
     */
    public function testNullOrStringsCanBeUsedAsADummyIfThereAreNoTypeHints()
    {
        $sut = new MostDynamicSut(null);
        $sut = new MostDynamicSut('this should not be called');
    }

    public function testNullCanBeUsedAsADummyIfItsTheDefaultForTypeHint()
    {
        // I prefer an explicit parameter; also there may be other parameters after that
        $sut = new TypeHintedWithDefaultSut(null);
    }

    public function testAnInlineClassCanBeUsedToInstantiateADummyObject()
    {
        $sut = new FullyDefensiveSut(new DummyIterator);
    }

    public function testADynamicallyGeneratedDummyObjectCanBeBuiltByPHPUnit()
    {
        $dummy = $this->getMock('Iterator');
        $sut = new FullyDefensiveSut($dummy);
    }

    public function testPHPUnitCanOverrideTheConstructorOfADummy()
    {
        $dummy = $this->getMockBuilder('ArrayIterator')
                      ->disableOriginalConstructor()
                      ->getMock();
        $sut = new FullyDefensiveSut($dummy);
    }

    public function testPHPUnitCanCheckThatTheDummyMethodsAreNeverCalled()
    {
     // often if someone calls it, lack of return value (null) causes explosion anyway.but if it's a void...
        $dummy = $this->getMock('Iterator');
        $dummy->expects($this->never())
              ->method('current');
        $sut = new FullyDefensiveSut($dummy);
    }
}


class MostDynamicSut
{
    public function __construct($iterator) { }
}

class TypeHintedWithDefaultSut
{
    public function __construct(Iterator $iterator = null) { }
}

class FullyDefensiveSut
{
    public function __construct(Iterator $iterator) { }
}

class DummyIterator extends ArrayIterator
{
    public function __construct() {}
}

Conclusion

 

This was the last pattern of the Practical PHP Testing Patterns series. Thanks for your attention during the journey: we got through the whole list of xUnit patterns and their application to PHP code.

If you're already missing this bi-weekly articles, don't worry: a new series is about to start...

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)