After learning to write tests and some good testing practices, it’s now time to look at mock objects.
When testing a class which needs an instance of another class to work, you do not want to depend on the other class too much. This is where mock objects come in – a mock object is a “clone” of an object, which we can use to simplify our tests, by having the mock object perform assertions or by replacing some functionality of the mock with our custom functionality.
In this post we’ll first look at how mock objects are created and used with PHPUnit, and then we’ll take a practical example of using mocks to test code which uses the database to fetch some data.
Mock basics
Before we look at how PHPUnit does mocks, let’s first check out the principle of how the mocks work.
You can create mocks even without the fancy methods PHPUnit provides to you – by simply creating a new class which extends the class you wish to mock:
class SomeClassMock extends SomeClass { } |
We could call that a mock object, although it’s not very useful yet.
Say the class which we are testing needs to call a method in SomeClass, and we want to be sure it actually gets called in our test. In this case, let’s add a variable and a method to our mock class:
class SomeClassMock extends SomeClass { public $methodWasCalled = false; public function aMethod() { //This method should get called by the object we are testing $this->methodWasCalled = true; } } |
Now, in our test, we can do something like this:
//Just a simple example public function testAMethodIsCalled() { //First, create a mock of SomeClass $someClass = new SomeClassMock(); //This is the object we are testing, let's assume //it takes a SomeClass instance as the parameter $object = new MyClass($someClass); //Say the method in SomeClass needs to be called now $object->doSomething(); //Confirm the method was called by checking the mock's variable: $this->assertTrue($someClass->methodWasCalled); } |
Creating mock objects with PHPUnit
Writing mock objects like we saw above is quite time consuming and complex. Luckily, we don’t have to actually write the mock classes ourselves, as PHPUnit provides us with a set of methods we can use to do almost any kind of mock we need. We will use the PHPUnit mocking API from now on, but it’s always useful to know how the things work behind the scenes.
Let’s rewrite the test with PHPUnit’s mocking API:
public function testAMethodIsCalled() { //First, create a mock of SomeClass $someClass = $this->getMock('SomeClass'); //Now we can tell the mock what we want to do: $someClass->expects($this->once()) ->method('aMethod'); //This is the object we are testing, let's assume //it takes a SomeClass instance as the parameter $object = new MyClass($someClass); //Say the method in SomeClass needs to be called now $object->doSomething(); //We don't need to do anything else - the mock object will confirm //that the method was called for us. } |
Doesn’t look too complex does it? Instead of writing a whole class to do a simple method-call check, we just use the getMock
method to receive a mock-version of an object, and then we tell it what it needs to do in this test.
What did we do to the mock?
First, we call the expects
method and pass $this->once()
as a parameter. Then, we call method
with 'aMethod'
. This means that the mock object “expects one method call to aMethod”. Replacing a method in the mock like this is also known as stubbing the method – the code in the method is replaced by a “test stub”.
What will happen if the method does not get called? You will get an error similar to this when you run your test:
1) testAMethodIsCalled(ExampleTest)
Expectation failed for method name is equal to <string:aMethod> when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
Using mocks to test database code
Now that we know our mocks, or at least enough to start with, let’s jump into some useful examples.
A very typical case is that you need to test code which uses the database – for example, the code might be fetching some data from the DB, or maybe inserting something. We certainly could have a testing database, but it’s much easier and simpler to work with mock objects.
The example scenario
Let’s say we have a class called EventRepository
which tracks some kind of events in our application. We also have a class called Event
, which represents a single event. We now want to write unit tests for the EventRepository
class, but as the class accesses the database we have a dilemma.
class EventRepository { private $_pdo; public function __construct(PDO $database) { $this->_pdo = $database; } public function findById() { $sql = 'SELECT * FROM events WHERE ...'; //Here we have some code to execute the SQL, convert //the result to an Event object and then return it } /* some other methods here */ } |
To test this class, we would need to use a test database. This is because the SQL queries etc. are executed inside the class and there’s nothing our test can do about it.
Making the EventRepository
testable
To make this class better, and easier to test, let’s modify it to use a data access object.
The data access object (DAO) will look something like this for our example:
class EventDao { private $_pdo; public function __construct(PDO $pdo) { $this->_pdo = $pdo; } public function findById() { $sql = 'SELECT * FROM events WHERE ...'; //Instead of event repository, the code to execute SQL is here. //We then return the row's data as an array } /* some other methods here */ } |
Then, the EventRepository
is modified to use EventDao
:
class EventRepository { private $_dao; public function __construct(EventDao $dao) { $this->_dao = $dao; } public function findById($id) { $row = $this->_dao->findById($id); //Now we simply process $row into an Event object. No direct DB access } /* some other methods here */ } |
Testing the thing
Now we can test the class! We mock out the EventDao
object, so the testing code for EventRepository
will not need access to a database.
Let’s write a test for the findById
method to demonstrate mocking the DAO:
public function testFindsCorrectEvent() { //Set up a fake database row $eventRow = array( 'id' => 1, 'name' => 'Awesome event' ); $dao = $this->getMock('EventDao'); //Set up the mock to return the fake row when findById is called $dao->expects($this->once()) ->method('findById') ->with(1) ->will($this->returnValue($eventRow)); $repo = new EventRepository($dao); $event = $repo->findById(1); //Confirm ID of the returned Event is correct $this->assertEquals(1, $event->getId()); } |
Here we have some more features of the mock API – checking parameters and deciding return values.
Firstly, the $eventRow
is a “fake” return value for the mocked dao. For the repository to work correctly, the dao will need to return it a value, and this is it.
Expects and method in the mock set up is the same as before, but the with(1)
and will($this->returnValue($eventRow))
are new.
with
is used to tell the mocked method what parameters it expects, in this case it should be passed the event’s ID: 1. If it is not passed the value 1 when called, it will report the test as a failure.
will
is used to tell the mocked method what it should do when it gets called, and we want it to return our fake row.
The rest of the test sets up the repository that we are actually testing, and calls a method and asserts that the return value was correct. We didn’t go over the rest of the code in findById
, nor did we give Event
a getId()
method, but just assume that there was some code in findById
which turns the row into an Event
object, and that Event
has the method.
Summing up and further reading
Using the PHPUnit mocking API we can easily replace dependencies in our tested classes with mocks that make testing much simpler, than having to depend on the actual object itself.
We didn’t cover all possible mock calls, such as how to have it throw an exception. For that and more, I recommend reading the PHPUnit manual stubs and mocks chapter.
In the next post, we’ll check out how to do test-driven or test-first style development