Mocking in PHPUnit
A practical guide to mocking in PHPUnit
First, what is a mock?
A mock is a piece of dummy code that helps your tests run in a way that isolates specific functionality.
e.g. we can mock out a function to return values in a way that makes a related piece of functionality either pass or fail a test.
An example of a mocked function would be a mimicked response from a database call with dummy data that matches what we would expect the database call returned data to look like.
This is useful in unit testing as we don’t actually want to call a database, but rather test a specific piece of functionality that would interact with an expected response from a database.
When should we use mocking?
For starters, some common elements that you might want to mock include:
- Database queries
- 3rd party libraries
- http requests
- Anything tightly coupled with the underlying hardware of the operating system (e.g. Date time related or reading and writing to a file on disk)
- Functions with unpredictable results (random numbers)
The common thread here is that we don’t necessarily want to test anything that is outside of the scope of the individual functions we write. However, it’s worth mentioning that sometimes you do want to test code that has been integrated with one of these “external” services, making sure that it does in fact work as expected. This falls into the realm of an integration test. An integration test is basically asserting that multiple key components work together as expected, e.g. your code works as expected using actual database calls.
Here I have chosen to focus specifically on unit testing, which follows on from this intro to assert with PHP Unit — focusing on testing a single “unit” of functionality.
Jumping into the code
For our example I am going to create a “person” class that can do specific person related things, one of those things is display the greeting message “Hello World”.
<?phpclass Person
{
public function greeting()
{
return 'Hello World';
}
}
Good stuff, so now we can create a new person and tell them to greet the world.
$person = new Person();
echo $person->greeting();
We can easily transform this into a basic test assertion:
require __DIR__ . '/Person.php';use PHPUnit\Framework\TestCase;class MockPerson extends TestCase
{
public function test_greeting()
{
$test = new Person();
$this->assertEquals('Hello World', $test->greeting());
}
}
Now I want to be able to query a list of users from a database and depending on the ID passed into my greeting function, return the corresponding name of that person in the database.
The database class would probably look something like this:
<?php<?phpclass Database
{
public function getPersonByID($id)
{
// do some stuff in the db to get a person by their ID
return sql("select * from person where id = $id limit 1;")[0];
}
}
We can now pull this into our person class and use it:
<?phpclass Person
{
public $db = null;function __construct($db)
{
$this->db = $db;
}public function greeting($id)
{
$friend = $this->db->getPersonByID($id);
$friendName = $friend->name;
return "Hello $friendName";
}
}require __DIR__ . '/Database.php';$db = new Database();
$person = new Person($db);// in our database we have a person with an ID of 2 and a name of "Bob"echo $person->greeting(2);// result should be "Hello Bob"
We don’t actually want to test that getPersonByID returns a result from our database, but instead that person->greeting(2)
gives us a result that we expect.
In theory, our test code would look like this (NB: this is an example of bad code):
<?phprequire __DIR__ . '/Person.php';
require __DIR__ . '/Database.php';use PHPUnit\Framework\TestCase;class MockPerson extends TestCase
{
public function test_greeting()
{
$db = new Database();
$test = new Person($db);
$this->assertEquals('Hello Bob', $test->greeting(2));
}
}
To avoid actually making a database query we can mock our database class within our test.
<?php
require __DIR__ . '/Database.php';
require __DIR__ . '/Person.php';use PHPUnit\Framework\TestCase;class MockTest extends TestCase
{
public function test_greeting()
{
$dbMock = $this->getMockBuilder(Database::class)
->setMethods(['getPersonByID'])
->getMock();$mockPerson = new stdClass();$mockPerson->name = 'Bob';
$dbMock->method('getPersonByID')->willReturn($mockPerson);$test = new Person($dbMock);
$this->assertEquals('Hello Bob', $test->greeting(2));
}
}
Let’s go through this one section at a time to see what’s going on:
Firstly we want to build a mock of the Database class that is exactly the same except we are telling the MockBuilder that we are going to get setting the getPersonByID
later on ourselves.
$dbMock
now becomes our $db
$dbMock = $this->getMockBuilder(Database::class)
->setMethods(['getPersonByID'])
->getMock();
Next we create a “fake” person object returned from the db that is just a standard class type with a name attribute of “Bob”:
$mockPerson = new stdClass();$mockPerson->name = 'Bob';
Next we tell the newly created mock class to return our “fake” person whenever the getPersonByID
function is called, instead of calling the real function that exists on the Database class:
$dbMock->method('getPersonByID')->willReturn($mockPerson);
Next we pass our mocked Database instance into our Person constructor as opposed to a real one:
$test = new Person($dbMock);
Finally we call our greeting
function which will in turn call our mocked Database instance during its execution.
$this->assertEquals('Hello Bob', $test->greeting(2));
Mocking out unrelated or external functionality is a great way to isolate only the code that you want to test.
The above example is quite simple in nature, and mocking can become complicated as a project grows. It takes a fair amount of intuition to learn the best way to implement mocking when writing tests, but this will become easier the more you practice.
I would be keen to hear any other thoughts or answer any questions you may have on the subject in the comments below.