Symfony Unit Testing Part 2 – Test Doubles

In the previous blog post we went over the basics of asserting. Another important concept is test doubles. I already went over the different kinds in this post.

So a test double is used to create a ‘fake’ version of an object. Our unit that we are testing might require data from the database. Preferable using a repository for this. We don’t want to test this repository and any database connection. We should be able to create a fake version of this repository based on its interface.

A test double thus is used to make our tests faster, and to not make a unit test depend on other units. One test’s purpose is to only test that single unit. Does this mean there are no exceptions? Well yes, you can depend on real objects. As long as they don’t influence your unit’s test performance and functionality.

Remember, tests need to be fast!

For creating test doubles we have 2 options. We can use Mockery or Prophecy. So lets go over the practical difference of both.

Creating the test double

To create a test double with mockery we need to use the createMock method.

class ProductCalculatorTest extends TestCase
{

    public function testMockeryProductCalculator(): void
    {
        $product = $this->createMock(ProductInterface::class);
    }
}

With prophecy we need to use prophesize.

class ProductCalculatorTest extends TestCase
{

    public function testProphecyProductCalculator(): void
    {
        $product = $this->prophesize(ProductInterface::class);
    }
}

You can see, so far it works the same. The difference is in how we create the test double.

Mocking or prophesize the methods

In the mockery example we mock that the method getPrice get called and will return 0. After that we expect setPrice to be called once with an argument of value 5. We are actually explaining how the increasePrice method of our product calculator should work.

$product->method('getPrice')->willReturn(0);
$product->expects($this->once())
    ->method('setPrice')
    ->with(5);

$productCalculator = new ProductCalculator();
$productCalculator->increasePrice($product, 5);

Now with prophesize we do it a bit differently. We can make it a bit more dynamic. We can actualy prophesize the behaviour of our fake object.

$product->getPrice()->shouldBeCalled();
$product->setPrice(Argument::type('float'))->will(function ($args) {
    $this->getPrice()->willReturn($args[0]);
});

$productCalculator = new ProductCalculator();
$productCalculator->increasePrice($product->reveal(), 5);

After using prophesize for a bit I prefer it over mockery. You can make your tests more dynamic and to write less test code. I only go back to mockery when there is no other option.

But there is a catch in Symfony. By default prophesize will not work. You will get an error: Error: Class ‘Prophecy\Prophet’ not found.

The reason for this is that the PHPunit bridge by default removes symfony/yaml and phpspec/prophecy. You can change the list of packages to be removed by executing your test with an extra environment variable:

SYMFONY_PHPUNIT_REMOVE=\"symfony/yaml\" ./vendor/bin/simple-phpunit

So what should we prophesize?

You should create test doubles for all the dependant classes of the class that you are testing. Unless the dependent class is something very basic and does not influence our tests to much. Creating a test double costs time. You need to wheight out what is the most optimal way to test. As always, you need to think before you code.