PHPUnit 4.5 and Prophecy

PHPUnit 4.5 has been released today. The most notable change in this new version of PHPUnit is the out-of-the-box support for Prophecy, a modern object mocking framework for PHP.

Spoilt for Choice? – PHP Mocking Frameworks

PHPUnit has had built-in support for creating test doubles for many years. This implementation was originally inspired by the first generation of mocking frameworks for Java. Since then mocking frameworks have evolved. Modern mocking frameworks are more intuitive to use, lead to more readable code, and may even allow for a clear separation of a test double's configuration and the actual test double object itself.

Like many users of PHPUnit I am not satisfied with the API of PHPUnit's own mocking framework. This dissatisfaction has lead to the development of alternative mocking frameworks for PHP such as Mockery, Phake, or Prophecy. If I were to create a new mocking framework today it would probably look a lot like Prophecy. Which is why PHPUnit 4.5 introduced out-of-the-box support for it.

At the moment there are no plans to deprecate the API of PHPUnit's own mocking framework. It is likely, though, that at some point in the future we will encourage developers to use Prophecy's API instead of the "classic" PHPUnit API. As you can use PHPUnit's own mocking framework and Prophecy within the same test suite (yes, you can even also use Mockery and Phake at the same time) there is no need to migrate existing tests to Prophecy's API should you decide to use it for new tests. Also note that there are some features such as test proxies in PHPUnit's own mocking framework that are outside the scope of the Prophecy project.

Prophets, Prophecies, and Revelations

At the core of Prophecy's philosophy are the terms prophet, prophecy, and revelation:

"Prophecy has been named that way because it concentrates on describing the future behavior of objects with very limited knowledge about them. But as with any other prophecy, those object prophecies can't create themselves – there should be a Prophet."

The prophet creates prophecies by prophesizing them. Test double objects are then created by revealing their respective prophecy. You can think of a prophecy as the configuration for a test double that is stored in an object separate from the object (revelation) that acts as the test double.

Confused? I know that I was when I first read the introduction to Prophecy. But allow yourself some time to get to grips with this terminology. It will make sense, and prove useful, eventually.

Dummies, Stubs, and Mocks

For the following examples we use the Reader interface and the Processor class shown below:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php
interface Reader
{
public function read ( ) ;
}

class Processor
{
public function process ( Reader $reader )
{
$reader -> read ( '123' ) ;

// ...
}
}

Dummy

We need to pass a Reader object to process() when we want to test this method of the Processor class. If, for the purpose of our test, we do not care about this Reader object then we can get away with just passing a dummy object that implements the Reader interface but that does not do anything:

1 2 3 4 5 6 7 8 9 10 11 12 13
<?php
class ProcessorTest extends PHPUnit_Framework_TestCase
{
public function testDataFromReaderCanBeProcessed ( )
{
$reader = $this -> prophesize ( 'Reader' ) ;

$processor = new Processor ;
$processor -> process ( $reader -> reveal ( ) ) ;

// ...
}
}

In the code above, $this->prophesize('Reader') creates a prophecy for our Reader object. This object is used to configure our test double. Once we are done configuring the test double (in this first example we just need a dummy object without configuration) we use $reader->reveal() to reveal the prophecy and create the actual test double object.

Stub

If, for the purpose of our test, we need the read() method of the Reader object to return a specific value then we need to use a stub:

1 2 3 4 5 6 7 8 9 10 11 12 13 14
<?php
class ProcessorTest extends PHPUnit_Framework_TestCase
{
public function testDataFromReaderCanBeProcessed ( )
{
$reader = $this -> prophesize ( 'Reader' ) ;
$reader -> read ( '123' ) -> willReturn ( 'value' ) ;

$processor = new Processor ;
$processor -> process ( $reader -> reveal ( ) ) ;

// ...
}
}

The $reader->read('123')->willReturn('value'); line configures the stub to return the string value when the read() method is called with '123' as its argument.

Mock

If, for the purpose of our test, we need to ensure that the read() method of the Reader object is invoked with a specific argument from the unit of code under test then we need to use a mock:

1 2 3 4 5 6 7 8 9 10 11 12 13 14
<?php
class ProcessorTest extends PHPUnit_Framework_TestCase
{
public function testDataFromReaderCanBeProcessed ( )
{
$reader = $this -> prophesize ( 'Reader' ) ;
$reader -> read ( '123' ) -> willReturn ( 'value' ) -> shouldBeCalled ( ) ;

$processor = new Processor ;
$processor -> process ( $reader -> reveal ( ) ) ;

// ...
}
}

If the process() method of the Processor class does not invoke the read() method on the mock as we expect it to then we get the output shown below:

PHPUnit 4.5.0 by Sebastian Bergmann and contributors.

F

Time: 42 ms, Memory: 3.75Mb

There was 1 failure:

1) Test::testDataFromReaderCanBeProcessed
Some predictions failed:
  Double\Reader\P1:
    No calls been made that match:
      Double\Reader\P1->read(exact("123"))
    but expected at least one.

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Behind the scenes, Prophecy generated code for the test double in the Double\Reader\P1 class. The Double namespace is used to hold such generated classes, Double\Reader is the namespace for such generated classes for our Reader class, and Double\Reader\P1 is the name of the generated class for the first (and in our example only) prediction.

FOSDEM ConFoo 2015: It's that time again