PHP & Symfony About PHP and Symfony2 development

A better PHP testing experience Part II: Pick your test doubles wisely

Posted on by Matthias Noback

In the introduction to this series I mentioned that testing object interactions can be really hard. Most unit testing tutorials cover this subject by introducing the PHPUnit mocking sub-framework. The word "mock" in the context of PHPUnit is given the meaning of the general concept of a "test double". In reality, a mock is a very particular kind of test double. I can say after writing lots of unit tests for a couple of years now that my testing experience would have definitely been much better if I had known about the different kinds of test doubles that you can use in unit tests. Each type of test double has its own merits and it is vital to the quality of your test suite that you know when to use which one.

"Mocks" and fragility

If you have followed my advice from the previous article, your unit tests are not assertion-centric anymore. Even better: they describe what is going on and what the outcome of each test should be. The assertions themselves are hidden from sight. But your tests may still be quite unreadable because they contain long lists of set-up code for all the mocks that your Subject Under Test (SUT) requires, like this:

$object = ...;

$validationViolationList = $this->getMock('ValidationViolationList');
$validationViolationList
    ->expects($this->any())
    ->method('count')
    ->will($this->returnValue(1));

$validator = $this->getMock('Validator');
$validator
    ->expects($this->once())
    ->method('validate')
    ->with($object)
    ->will($this->returnValue($validationViolationList);

Code like this is pretty hard to read, although you will get used to it when you use PHPUnit for a long time. Besides readability issues, this code has some more problems:

  • It is not clear which types of test doubles are used.
  • It is not at once clear that the resulting validator "mock" never successfully validates an object, when called it will always return a violation list with one violation.

There are some more concerns or questions that need to be answered about this code:

  • Is it important that the validate() method is called exactly once? Shouldn't it be possible to call it twice and get the same result in both cases?
  • Is it even necessary to check the argument ($object) which is used when the validate() method is called)? And are you aware that by default PHPUnit checks only equality of the argument, not sameness (which in particular matters when the actual object and the expected object should have been exactly the same object).

Why do I have these kinds of concerns? It turns out that if you are not aware of these issues, mocks can badly influence the stability of your test suite. If you are too rigid about the number of times a method is called, the order in which methods are called or the arguments used when calling a method, your test will easily fail whenever you make structural changes to the SUT. When you refactor the code of the class you are testing, it's possible that the order of function calls changes or that maybe one function is called twice. This is why you want to build some flexibility into your test doubles (let's call them "test doubles" from now on, that will make things much more clear).

Different types of test doubles

I already mentioned that there are many different types of test doubles that you can use in your tests (there is a nice article about these types of test doubles by Robert Martin, called The Little Mocker. Let us briefly discuss each of them.

Dummy

When a particular type of object is required as an argument, but it is not used in any significant way, use a dummy. With PHPUnit:

// Logger is an interface
$logger = $this->getMock('Logger');

Whenever a method is called on the $logger dummy, it will return null. Be careful though: this only works when getMock() is called with the name of an interface. When a class name is provided, you also need to specify all the methods that PHPUnit should override to return null, otherwise PHPUnit will just call the existing methods of that class:

// Logger is a class
$logger = $this->getMockBuilder('Logger')
    ->setMethods(array('debug', 'info', ...))
    ->getMock();

Stub

When a test double is supposed to return some fixed values, you need a stub. The characteristics of a stub are:

  • It does not matter which arguments are provided when one of its methods is called.
  • It does not matter how many times a method is called.

With PHPUnit a stub looks like this:

$logger = $this->getMock('Logger');
$logger
    ->expects($this->any())
    ->method('getLevel')
    ->will($this->returnValue(Logger::INFO));

Both of the characteristics of a stub can be recognized in this particular code sample: no list of required arguments has been provided (using ->with(...)), so the arguments that are used will not be validated. Also $this->any() is used to indicate that there is no limit to the number of times this method is called and it will always return the same value: Logger::INFO.

Fake

A fake is a particular type of test double that looks a lot like a dummy, because:

  • It does not matter how many times a method is called.
  • It does not return null, like a dummy does.

But a fake has some simple logic by which it can decide which value it should return. An examples of a fake with PHPUnit:

$urlsByRoute = array(
    'index' => '/',
    'about_me' => '/about-me'
);

$urlGenerator = $this->getMock('UrlGenerator');
$urlGenerator
    ->expects($this->any())
    ->method('generate')
    ->will($this->returnCallback(
        function ($routeName) use ($urlsByRoute) {
            if (isset($urlsByRoute[$routeName])) {
                return $urlsByRoute[$routeName];
            }

            throw new UnknownRouteException();
        }
    ));

There are also simpler fakes, which just return a particular argument:

$urlGenerator = $this->getMock('UrlGenerator');
$urlGenerator
    ->expects($this->any())
    ->method('match')
    ->will($this->returnArgument(0));

A word of advice: you should limit the complexity of your fake's code. Pretty soon you will need unit tests for them too! In my experience, whenever I write code for a fake, I almost always accidentally introduce some kind of obscure bug that takes quite some debugging time to uncover.

Spy

If you want to keep track of method calls being made to a test double, to later examine them and make sure that the methods were called the expected number of times, in a particular order or with the correct arguments, you should use a spy. With PHPUnit this looks like:

$collectedMessages = array();

$logger = $this->getMock('Logger');
$logger
    ->expects($this->any())
    ->method('debug')
    ->will($this->returnCallback(
        function ($message) use (&$collectedMessages) {
            $collectedMessages[] = $message;
        }
    );

// inspect the collected messages

As you can see, the closure merely collects the $message argument whenever the debug() method is called. The closure returns nothing, so the return value of calling debug() will be null. Of course you could combine spy and stub in one test double and return some fixed value here (or anything else really, see also the next section).

Free spies!

There's one nice trick I'd like to share at this point. You don't really need to write spies in this particular way. You can just as well use an undocumented feature of PHPUnit here (read more about it in this article: Spying With PHPUnit).

$logger = $this->getMock('Logger');
$logger
    ->expects($spy = $this->any())
    ->method('debug');

$invocations = $spy->getInvocations();

$this->assertSame(1, count($invocations));

$firstInvocation = $invocations[0];
$this->assertEquals('An error occurred', $firstInvocation->parameters[0]);

Pretty cool, though until now I never felt the need to use a spy like this. The need for a spy can also be a test smell, telling you that the communication between your objects is just too complicated to test with simpler types of test doubles.

Mock

A mock is a type of test double that is actually very much like a spy:

  • It keeps track of method calls and their arguments.

But a mock also:

  • Validates method calls given a certain set of expectations.
$logger = $this->getMock('Logger');
$logger
    ->expects($this->at(0))
    ->method('debug')
    ->with('A debug message');
$logger
    ->expects($this->at(1))
    ->method('info')
    ->with('An info message');

True mocks can be recognized by the usage of $this->at(...), or $this->once(), $this->exactly(...), $this->atLeastOnce(), $this->never(). They often have $this->with(...) which will cause the arguments to be validated.

Mocks don't need to return a value, though they can be configured to do so of course, by turning them into a stub or a fake.

Don't overuse the "mocking" tool

Now that you know all about test doubles and how you can define them using PHPUnit's mocking tool, the advice I'd like to give is to not use it whenever you can. The biggest reason is that mocks created by tools like PHPUnit are very strange things. Test doubles like these:

  • Are created using a combination of code generation and eval() and thus only exist in memory.
  • Are therefore difficult to debug using a tool like XDebug (which can not "step through" them).

Besides, it takes some time to figure out all the special things you need to consider when you define test doubles:

  • with() compares arguments by equality, not sameness.
  • setMethods() requires an array. You will not get a clear error if you don't give it one.
  • getMock() with a class name as the first argument requires you to explicitly specify the methods you want to override.
  • at(...) starts with 0.
  • ...

All of this knowledge is usually only acquired by hard work and lots of debugging.

Manually create your test double classes

It is often much easier to just write a simple class that does exactly what you expect from it. Such a class is not hidden from sight, and it is even testable. These are some of the test doubles from the code samples above, rewritten using plain old PHP classes:

class LoggerDummy implements Logger
{
    public function debug()
    {
        return null; // not even necessary here
    }

    ...
}

class InfoLevelLoggerStub implements Logger
{
    ...

    public function getLevel()
    {
        return self::INFO;
    }
}

class PredefinedUrlsUrlGeneratorFake implements UrlGenerator
{
    private $urlsByRoute;

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

    public function generate($routeName)
    {
        if (isset($this->urlsByRoute[$routeName])) {
            return $this->urlsByRoute[$routeName];
        }

        throw new UnknownRouteException();
    }
}

I recommend to choose class names which describe the behavior of the particular test double class.

When it comes to spies and mocks, things will become a bit harder to implement manually. Still, if you need to track just some method calls or arguments, you could implement a spy by yourself and perform validation inside the test method.

The advantages of manually creating test doubles are:

  • You can use XDebug to step through their code because the code of the test doubles can be found in regular PHP files.
  • You make your test code much more readable: instead of lots of set-up code for your test doubles, you just instantiate a class with a descriptive name.
  • If your fakes have complicated code (which is still simpler than the code of the original class of course!), you can even write unit tests for them.

If you don't use test double classes: at least use factory methods

As I mentioned already, not every type of test double is best defined in a separate class. For some types of test doubles, like dummies (in particular dummies of interfaces), mocks and spies, you are still better off with a test double library like PHPUnit's mocking sub-framework. In this case your test classes will still contain mock set-up code that is difficult to read, understand and maintain. The solution is easy: hide the the highly technical set-up code from sight and put it in a private factory method:

public function testSomething()
{
    $logger = $this->loggerDummy();
}

private function loggerDummy()
{
    return $this->getMockBuilder('Logger')
        ->setMethods(array('debug', 'info', ...))
        ->getMock();
}

Conclusion

In this article we looked at test doubles and how they come in different shapes. Whenever you use PHPUnit's $this->getMock() ask yourself what type of test double you really need. Depending on your choice, consider replacing the dynamic in-memory test double with a concrete class manually created for this particular situation. If you don't choose that path, at least hide the construction details of the mocks from sight and use descriptive names.

Another article will be needed to clarify the use of test doubles (or other stand-ins) when testing classes that depend on other, third-party classes.

Prophecy

This article is based on PHPUnit's mocking sub-framework. There are several other libraries out there which can be used to create test doubles on-the-fly. After looking around a bit, the most interesting one is Prophecy. Its README file contains a nice explanation of test double types and how they can be created using the library. The library itself makes a lot of sense. It's also easy to integrate Prophecy with PHPUnit: there is another library which contains a base test class from which you can extend your own test classes.

Categories: PHP Testing

Tags: PHPUnit unit testing

Comments: Comments