Skip to Content

Mocking Dependencies in PHP Unit Tests with Mockery

Posted on    5 mins read

The following solution is based on the work of my coworker Marcel Frank.

The PHP library Mockery allows to use a simulated version of certain objects within unit tests, where objects are passed into methods as dependencies. This form of simulation is called mocking.

Instead of trying to explain why simulating these objects in the tests makes sense, I will try to illustrate the need for it with an example.

The following class is responsible for getting news headlines from a database and returning an array of these headlines in a certain format. It has its own share of logic, but clearly depends on a database connection object in order to do its job:

class NewsReader
{
    public function getAllHeadlinesUppercase(\Doctrine\DBAL\Connection $dbConnection)
    {
        $headlines = array();

        $statement = $dbConnection->executeQuery('SELECT headline FROM news ORDER BY id DESC');

        while ($row = $statement->fetch()) {
            $headlines[] = strtoupper($row['headline']);
        }

        return $headlines;
    }
}

The dependency on the \Doctrine\DBAL\Connection object makes it difficult to test this class: preferably, we would like to only test the behaviour of the method itself: does it query the database, and does it transform (and return) the result as it should? It would be overkill, and actually quite annoying, if we had to set up an actual database server that has the table we need, which in turn would need to have some useful sample data, only to make sure that this simple method behaves correctly.

The solution here is to pass a simulation of $dbConnection into this method, instead of the real thing. The real thing needs a real database to do its job – the simulation, or mock, of a \Doctrine\DBAL\Connection looks and feels like a real connection object, but its behaviour is only a realistic fake. This way, getAllHeadlinesUppercase can use it, but we don’t need the database infrastructure for our test.

Let’s first look how a unit test for our class would look like if we would not use mocking:

class NewsReaderTest extends \PHPUnit_Framework_TestCase
{
    public function testGetAllHeadlinesUppercase()
    {
        $dbConnection = ... // Do whatever is needed to create a real connection to a real database

        $newsReader = new NewsReader();

        $expectedHeadlines = array('FIRST HEADLINE', 'SECOND HEADLINE');
        $actualHeadlines = $newsReader->getAllHeadlinesUppercase($dbConnection);

        $this->assertEqual($expectedHeadlines, $actualHeadlines);
    }
}

Straight forward, but as said, it would need a running database that carried the two headlines in order to work.

Here is a version of the test where we replace the database connection object with a mock:

class NewsReaderTest extends \PHPUnit_Framework_TestCase
{
    public function testGetAllHeadlinesUppercase()
    {
        $mockedDbConnection = \Mockery::mock('\Doctrine\DBAL\Connection');

        $mockedStatement = \Mockery::mock('\Doctrine\DBAL\Driver\Statement');

        $mockedDbConnection
            ->shouldReceive('executeQuery')
            ->with('SELECT headline FROM news ORDER BY id DESC')
            ->andReturn($mockedStatement);

        $mockedRows = array(
            array('headline' => 'First headline'),
            array('headline' => 'Second headline')
        );

        $mockedStatement
            ->shouldReceive('fetch')
            ->andReturnUsing(function () use (&$mockedRows) {
                $row = current($mockedRows);
                next($mockedRows);
                return $row;
            });

        $newsReader = new NewsReader();

        $expectedHeadlines = array('FIRST HEADLINE', 'SECOND HEADLINE');
        $actualHeadlines = $newsReader->getAllHeadlinesUppercase($mockedDbConnection);

        $this->assertEquals($expectedHeadlines, $actualHeadlines);
    }
}

Let's work through it step by step in order to understand what is happening.

We start by building our mock objects. First $mockedDbConnection; to do so, we call Mockery::mock, which returns a mock object based on the class name we pass it.

We also need to mock a \Doctrine\DBAL\Driver\Statement object, because getAllHeadlinesUppercase will call $mockedDbConnection::executeQuery which returns a statement object of this type; subsequently, getAllHeadlinesUppercase calls fetch on this statement object, and therefore, this has to be simulated, too.

Now comes the interesting part: we programatically define the behaviour of our simulated objects. Things that would "just happen" if we had a real database need to be defined by us now - this makes our mocks behave realistically enough so getAllHeadlinesUppercase won't realize the difference and take the simulation objects for the real thing.

Mockery provides a nice API for defining simulated behaviour. Let's have a closer look at the first definition:

        $mockedDbConnection
            ->shouldReceive('executeQuery')
            ->with('SELECT headline FROM news ORDER BY id DESC')
            ->andReturn($mockedStatement);

What happens here is: we define how the $mockedDbConnection object will behave when its method executeQuery is called. We also implicitly express an expectation: we expect getAllHeadlinesUppercase to call this method with a certain parameter (the query), and if it would call executeQuery differently, then Mockery would make our test case fail.

We also declare what executeQuery should return if it is called. The real object would return a real statement object - our mock will return a mocked statement object, which is set up as follows:

        $mockedRows = array(
            array('headline' => 'First headline'),
            array('headline' => 'Second headline')
        );

        $mockedStatement
            ->shouldReceive('fetch')
            ->andReturnUsing(function () use (&$mockedRows) {
                $row = current($mockedRows);
                next($mockedRows);
                return $row;
            });

The code using our mocks still expects rows to be returned when calling fetch() on the statement object. We simulate this behaviour by iterating through an array we declare right in our test case, which gives us full control over the data the code-under-test receives.

What then follows is the test case as we know it. We instantiate the newsReader object, and this time, we pass it our prepared mock database connection object instead of the real one:

        $newsReader = new NewsReader();

        $expectedHeadlines = array('FIRST HEADLINE', 'SECOND HEADLINE');
        $actualHeadlines = $newsReader->getAllHeadlinesUppercase($mockedDbConnection);

        $this->assertEquals($expectedHeadlines, $actualHeadlines);

As said, the bonus achievement here is that not only can we simulate a database and verify that the getAllHeadlinesUppercase method does the right thing in the face of a given set of table rows, we also verify that it correctly queries the database. In case you change the SELECT statement, either the expected one in the test case or the actual one in the getAllHeadlinesUppercase method, Mockery will throw an exception because it can no longer find a matching method definition call in the mock setup.