In order to build high quality software products, unit testing is an essential building block. However, it is still not uncommon that large software projects don’t implement unit testing properly.
A common failure is that a unit test does not concentrate on one thing but rather tests a whole feature. The problem is that large projects usually have a lot of dependencies which makes it very hard to verify the correct behavior of just a tiny part. This is were the concept of mocking comes into play. When mocking dependencies we can setup expectations and assume return values on objects we cannot rely when executing a particular unit test. This helps us to arrange and simulate the desire setup for the code to test. Today I want to provide a short introduction on how this can be done in PHP with Mockery, a mocking library which can be used with popular test libraries like PHPUnit or PHPSpec.
Installation
Having the PHP package manager composer installed you just have to execute composer require mockery/mockery
to grab the latest version and you are good to go.
Creating mock objects and simple expectations
The first step is to create a mock object which stands for an actual object and simulates its behavior. Creating such an object is really simple. Let’s assume you have a service which provides you with weather information your program uses to do some calculations on. You can create a mock of this service as following:
$weatherServiceMock = \Mockery::mock(WeatherService::class);
The WeatherService can be a class, an abstract class or an interface, like you often have it when working with Dependency Injection where you bind a concrete class at runtime.
Since we now have our mock object we have to do something with it. The most common use case is that we want to verify that our code to be unit tested calls a particular method of the WeatherService and does something with the result. This is called setting up an expectation. Let’s assume that our WeatherService exposes a getTemperature()
function which returns the current temperature. An expectation would look like that:
$weatherService->shouldReceive('getTemperature')->once()->andReturn(20.0);
You can probably guess what this line means. We want to verify that the method is called and tell it to return the value 20.0. This value is now be used by our subsequent code and we can add a meaningful assertion for the end result of our unit test. If we omit theandReturn(20.0)
modifier the return value will be null
. We can also verify that a function does not get called by using
$weatherService->shouldReceive('getTemperature')->never();
or the shorthand version $weatherService->shouldNotReceive('getTemperature');
.
Argument expectations
The first two expectation examples just verified that a method got called or not. This is often too unspecific and we want to verify that the method is called with the correct values for the arguments to be passed. Let’s assume again our getTemperature
method but this time it takes the desired unit as parameter. The code we want to test calculates the unit somehow and passes it to the WeatherService. Let’s verify that it does this correctly.
$weatherService->shouldReceive('getTemperature')->once()->with('degree')->andReturn(20.0);
If you want to verify more parameters just add them split by semicolon or use the alternative withArgs(['param1', 'param2'])
syntax.
Actually you can do a few more useful things with argument matching in Mockery whereby a cool thing might be the passing of closures which check whether arguments fulfill some criterion. As an example we could only allow that the passed temperature unit is either degree or fahrenheit. This could look like following:
$weatherService->shouldReceive('getTemperature')
->once()
->withArgs(function ($arg) {
return $arg === 'degree' || $arg === 'fahrenheit';
})
->andReturn(20.0);`
Call count expectations
Sometimes it’s useful to not only verify that a method gets called or not but also how often it is called exactly. There are different variations to check the number of times a method gets called. Following examples using the imaginary WeatherService demonstrate that.
$weatherService->shouldReceive('getTemperature')->twice()->andReturn(20.0);
$weatherService->shouldReceive('getTemperature')->times(3)->andReturn(20.0);
$weatherService->shouldReceive('getTemperature')->zeroOrMoreTimes()->andReturn(20.0);
$weatherService->shouldReceive('getTemperature')->atLeast()->times(3)->andReturn(20.0);
$weatherService->shouldReceive('getTemperature')->atMost()->times(3)->andReturn(20.0);
You can see that this is indeed quite flexible and sometimes very useful.
Partial mocks
You have already seen a lot of examples on how you can set expectations for a mock object. Actually it’s quite unproblematic and an easy thing to do. However there can be situations where things get a bit more tricky. As I was in this situation quite early when working with unit testing in PHP, I want to shortly take an eye onto partial mocking. Frankly spoken, partial mocks are useful when you have your mock object and don’t want to setup expectations on all the methods of this mock object called by your code under test.
Due to reasons of simplicity I stick to the WeatherService example I have used throughout the post although it might not be that realistic for the following example. Let’s assume next to the getTemperature
method there is also a getWindSpeed
method called by the code under test. If we keep the single expectation $weatherService->shouldReceive('getTemperature')->once()->andReturn(20.0);
our unit test will fail with an error that an expectation is missing. Since we defined our WeatherService as mock object, Mockery requires us to setup expectations for all methods of the object. However, we want to execute the implementation of the getWindSpeed
method. The solution is a so called partial mock. As the name implies it can be used to only mock parts of the methods and delegate to the actual implementation for the rest. A partial mock can be defined quite similar to a normal one but just extended by an additional modifier like so: $weatherServiceMock = \Mockery::mock(WeatherService::class)->makePartial();
Now all methods which do not have an explicit expectation setup are called.
Another modifier when defining a mock object is shouldIgnoreMissing()
. This tells the Mockery that no exception has to be thrown if an expectation is not set for a method but rather it should ignore it and just return null. This might be useful if you do not care about the result of a method but also don’t want to delegate to the actual implementation since you cannot rely on the dependent component.
Conclusion
I hope this blog post showed you the essence of mocking with Mockery in PHP and motivated you to write better unit tests. There are definitely good mocking libraries in most of the popular programming languages available. I like the easy syntax of Mockery and they are now a fixed part of my repertoire in PHPUnit. As you probably suppose Mockery offers more things which did not find it’s place in this blog post. I highly recommend the official documentation which this post is mainly based on and where you can find more details.