Jul 22, 2013
Table of contents:
In last week’s post I started using Mockery to test my Controllers. Mocks are a very powerful concept and something that you will be using a lot throughout your career to create robust and testable applications.
In this post I’m going to take a deep dive on Mockery, why mocks are so important, how to use Mockery and what are some of the common things you can use Mockery for.
Before I get into showing you how great Mockery is and how you should use it in your application, first it’s important that you fully understand why we need to use Mock Objects and how they work.
A Mock Object is basically just a simulated version of a real object from within your application. Once your application reaches a certain level of complexity, certain objects will depend on certain other objects. By simulating the dependent objects, we can isolate only the things we want to test.
For example, when we are testing a Controller, we want to ensure that the Controller can return data from a database, but we have no need to actually touch the database. The Controller does not care if the database is working correctly or not because the Model Unit tests should catch any problems with the database. It is not the Controller’s responsibility to test the database. Instead we can just simulate the database using a Mock Object. The Mock Object behaves the same way as the database, but it has none of the overhead.
Using Mock Objects is beneficial for a number of reasons.
Isolate - Firstly you should always isolate your tests so you are only testing one thing at a time. Running tests with dependencies can result in unexpected results.
Non-deterministic results - If you are testing something that relies on non-deterministic results, this problem can be resolved easily with Mock Objects. For example, if you send an automatic email once an hour, you can mock this situation very easily.
Different states - If you need to run tests under different states this can be solved by using Mock Objects. For example, if you are relying on a third party API, it can be very easy to mock that the API is down and is giving you an error.
Speed - Once you have a full blown application, you could possibly have thousands of tests. When following good Continuous Integration principles, all of those tests will need to be run every time there is a commit to the master branch. Running your tests through a real database can add significant run time to your tests, whereas mocking the database is much quicker.
No existent objects - If you are following good Test Driven Development practice, the object that you depend on might not even exist yet. Again, Mock Objects can easily mock the methods that you require without you having to actually create the object. This can be great for quickly testing out an idea.
Mock Objects are simply an object that mimics the real object. You can tell the mock object what methods you want to call, in what order and what parameters it should accept. You can then tell the Mock Object what you expect it to return.
When you use Mock Objects, your code does not care whether you are using the real dependency or a mocked object. You are only testing how your test object responds to the result of the Mock object.
So hopefully that has given you a good background as to what are Mock Objects and why they should be used.
Mockery is a powerful Mock Object framework for PHP testing. Although PHPUnit does actually ship with a Mock Object library, I think most people prefer to use Mockery instead.
Mockery is very easy to pick up and it can be installed as a dependency of the project through Composer so it’s really easy to get going with.
If you missed last week’s tutorial, here’s how to install Mockery.
First add Mockery to your composer.json
file:
{
"require-dev": {
"mockery/mockery": "dev-master@dev"
}
}
Next run the install
command from terminal:
composer.phar install -dev
Before we can start using Mockery in our actual tests, first we need to create some set up methods to prepare the stage.
First create a new test file named UserControllerTest.php
. In this test file I’m going to be testing the methods of the User Controller.
Next add the following method:
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('Cribbb\Storage\User\UserRepository');
}
parent::setUp();
is called because we are overwriting the method of the parent class.
Next I set the $this->mock
property of the class to the return value of the $this->mock();
method.
The $this->mock();
is as follows:
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
This method accepts the name of the class that you want to mock.
First it creates a new mock.
Next I’m binding the mock to the name of the class in Laravel’s IoC container. This means, whenever Laravel is asked for this class, it will return my mocked version instead of the real thing.
And finally the method returns the mock.
So now in the setUp()
method, you can create any mock objects that you require in this test file.
Between each test, you need to clean up Mockery so that any expectations from the previous test do not interfere with the current test.
To do that, we can simply create a tearDown()
method:
public function tearDown()
{
Mockery::close();
}
The static method close()
cleans up the Mockery container used by the current test, and runs any verification tasks needed for your expectations.
Mockery is a simple, yet powerful framework for creating Mock Objects. I’m not going to show you every possibility of using Mockery in your tests because I would be on forever.
Instead I’m going to show you some of the more common uses that you will find creep up more often, and some interesting things you can do with Mockery to write better tests.
The most common thing you will do with Mockery is set the shouldReceive(method_name)
method. This sets Mockery’s expectation for which method you want to call on the Mock Object.
For example, here I’m calling the all()
method once on the Mock Object:
public function testIndex()
{
$this->mock->shouldReceive('all')->once();
}
As shown above, the once()
method will call the expected method once on the Mock Object. twice()
will obviously call the method twice, and times()
allows you to specify N times.
For example, say you wanted to find the average amount of Twitter followers of random people, you could use something along the lines of:
public function testAverageTwitterFollowers()
{
$twitter = m::mock('twitter');
$twitter->shouldReceive('followers')->times(3)->andReturn(203, 344, 4524);
$analytics = new Analytics($twitter);
$this->assertEquals(2055, $analytics->average());
}
The with()
method adds a constraint that this expectation only applies to method calls which match the expected argument list.
This means that the expectation only applies to the method when it is called with these exact arguments. This allows you to test methods under different circumstances.
For example, say you had a method that accepts input and validates that input against a default set of validation rules. The method might accept an optional closure that would overwrite those default validation rules. Using the with()
method you could mock both of these expectations.
the andReturn()
method allows you to set a return value from the Mock. This is useful when you want to assert that the stage has been correctly set for the view.
For example:
public function testArchiveHasPosts()
{
$this->mock
->shouldReceive('all')
->once()
->andReturn('foo');
$this->app->instance('Post', $this->mock);
$this->call('GET', 'posts');
$this->assertViewHas('posts');
}
In this instance, it doesn’t matter what is actually returned, we are only asserting that the View has the returned value.
The never()
method is useful for asserting that a method should never be called. This is useful to ensure that under certain conditions, your method is following the correct path.
For example, say we had a method that should only be run if the return value of another method is true, we could test it like this:
public function testDoesNotRequestUpdates()
{
$this->mock
->shouldReceive('expired')
->once()
->andReturn('false');
$this->mock
->shouldReceive('update')
->never();
$this->app->instance('Request', $this->mock);
$this->call('GET', 'request/update');
}
Sometimes you will only want to mock certain methods on an object and let the rest work as normal. This can be achieved using Partial Mocks.
A traditional partial mock is where you define which methods you want to mock when the mock is created:
$this->mock = Mockery::mock("MyClass[save, send]");
In this instance, only the save();
and send();
methods will be mocked and all the rest of the methods on the object will work as normal.
You can also set passive mocks, like this:
$this->mock = Mockery::mock("MyClass")->makePartial();
This method does not require that you declare which methods to mock ahead of time.
With both forms of partial mocks, you need to set the expectations of the methods that you do want to mock.
$this->mock
->shouldReceive("save")
->once()
->andReturn("true");
This is important because you need to remember to dictate their mocked behaviour.
There is much, much more that you can do with Mockery than what I’ve covered in this tutorial. Mockery is such an extensive framework that it should cover 99% of all situations that you want to mock.
I would highly recommend reading through the documentation for Mockery. It can seem overwhelming at first so don’t try and take it all in at once. The more you use Mockery the more you will understand it.
I find getting the basics down is best at first because you will keep using the same calls over and over again. It’s only when you need a more complicated expectation should you start exploring the finer possibilities of Mockery.
Remember, you only need what you need and nothing more!
This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.