Félix Delval
Published

Thu 16 April 2015

←Home

Unit test with behavioural mock

In the previous artictle, we talked about a way to write more expressive unit test. Doing this we actually deffined the behaviour of our injected mocks.

So if we push this logic further, we should actually start extracting this code away from the test class to a pure behavior mock class.

Let's see how that would take us with the cassette player example.

<?php

namespace My\App\BehavioralMock;

class CassetteInterfaceBehavioralMock extends \PHPUnit_Framework_TestCase
{
    const MOCKED_CLASSNAME = 'My\App\CassetteInterface';

    private $counter;
    private $mock;

    public function willNotBeOver()
    {
        $this->getMockObject()->expects($this->at($this->counter++))
            ->method('isOver')
            ->willReturn(false);
    }

    public function willBeRead()
    {
        $this->getMockObject()->expects($this->at($this->counter++))
            ->method('read');
    }

    public function willBeOver()
    {
        $this->getMockObject()->expects($this->at($this->counter++))
            ->method('isOver')
            ->willReturn(true);
    }

    public function willBeRewinded()
    {
        $this->getMockObject()->expects($this->at($this->counter++))
            ->method('rewind');
    }

    public function getMockObject()
    {
        if ($this->mock === null) {
            $this->mock = $this->getMock(self::MOCKED_CLASSNAME);
        }

        return $this->mock;
    }
}

And now the test look small, concise and precise :

<?php

namespace My\App\BehavioralMock;

use My\App\CassettePlayer;

class CassettePlayerBehavioralTest extends \PHPUnit_Framework_TestCase
{
    public function testPlayAndRewind()
    {
        $cassette = new CassetteInterfaceBehavioralMock();

        $cassette->willNotBeOver();
        $cassette->willBeRead();
        $cassette->willBeOver();
        $cassette->willBeRewinded();

        $player = new CassettePlayer($cassette->getMockObject());

        $player->playAndRewind();
    }
}

What are the advantages of this methods?

Well for a starter this code is extremely maintainable and reusable. It makes a lot of sense to use this technique for interfaces that are used in multiple places.

The test is extremely readable. So readable it almost looks like a human readable documentation of the code.

Is there any disadvantage?

The obvious one is that all your unit test code are now sharing this class and any undesirable change to it will compromise all of your unit tests.

The code from the examples is available here

Go Top
comments powered by Disqus