Mock your Test Dependencies with Mockery

on

This article is written for and published on SitePoint.

Although not everyone is doing it yet, testing your application is one of the most essential parts of being a developer. Unit tests are the most common tests to run. With unit tests, you can check if a class behaves exactly like you intended it too. Sometimes, you are using a third party service within your application and it’s hard to get everything set up to get this unit tested. That’s exactly when mocking comes into play.

What is mocking?

Mocking an object is nothing more than creating a stand-in object, which replaces the real object in a unit test. If your application heavily relies on dependency injection, mocking is the way to go.

There can be several reasons to mock objects

  1. When performing unit tests, it’s best to isolate the class. You don’t want another class or service to interfere with your unit test.
  2. The object doesn’t exist yet. You can first create the tests, then build the final objects.
  3. A mock object is generally faster than preparing a whole database for your test.

When running unit tests, you are probably using PHPUnit. PHPUnit comes with some default mocking abilities as you can see in the documentation. You can read more about mocking in general and the mocking abilities from PHPUnit in this article written by Jeune Asuncion.

In this article, we will dive into Mockery, a library created by Pádraic Brady. We will create a temperature class which gets a currently non existing weather service injected.

Setup

Let’s start by setting up our project. We start off with a composer.json file which contains the following content. This will make sure we have mockery and PHPUnit available.

{
    "name": "sitepoint/weather",
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "psr-0": { "": "src/" }
    },
    "require-dev": {
        "phpunit/phpunit": "4.1.*",
        "mockery/mockery": "0.9.*"
    }
}

We also create a PHPUnit config file named phpunit.xml

<phpunit>
    <testsuite name="SitePoint Weather">
        <directory>src</directory>
    </testsuite>
    <listeners>
        <listener class="MockeryAdapterPhpunitTestListener"
                  file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php">
        </listener>
    </listeners>
</phpunit>

It’s important to define this listener. Without the listener, methods like once(), twice() and times() won’t thrown an error if they are not used correctly. More on that later.

I also created 2 directories. The src directory to keep my classes in and a tests directory to store our tests. Within the src directory, I created the path SitePointWeather.

We start off by creating the WeatherServiceInterface. Our non existing weather service will implement this interface. In this case, we only provide a method which will give us a temperature in Celsius.

namespace SitePointWeather;

interface WeatherServiceInterface
{
    /**
     * Return the Celsius temperature
     *
     * @return float
     */
    public function getTempCelsius();
}

So, we have a service which provides us with a temperature in Celsius. I would like to get the temperature in Fahrenheit. For that, I create a new class named TemperatureService. This service will get the weather service injected. Next to that, we also define a method which will convert the Celsius temperature to Fahrenheit.

namespace SitePointWeather;

class TemperatureService
{
    /**
     * @var WeatherServiceInterace $weatherService Holds the weather service
     */
    private $weatherService;

    /**
     * Constructor.
     *
     * @param WeatherServiceInterface $weatherService
     */
    public function __construct(WeatherServiceInterface $weatherService) {
        $this->weatherService = $weatherService;
    }

    /**
     * Get current temperature in Fahrenheit
     *
     * @return float
     */
    public function getTempFahrenheit() {
        return ($this->weatherService->getTempCelsius() * 1.8000) + 32;
    }
}

Create the unit test

We have everything in place to set up our unit test. We create a TemperatureServiceTest class within the tests directory. Within this class we create the method testGetTempFahrenheit() which will test our Fahrenheit method.

The first step to do within this method is to create a new TemperatureService object. Right at the moment we do that, our constructor will ask for an object with the WeatherServiceInterface implemented. Since we don’t have such an object yet (and we don’t want one), we are going to use Mockery to create a mock object for us. Let’s have a look how the method would look like when it’s completely finished.

namespace SitePointWeatherTests;

use SitePointWeatherTemperatureService;

class TemperatureServiceTest extends PHPUnit_Framework_TestCase 
{

    public function testGetTempFahrenheit() {
        $weatherServiceMock = Mockery::mock('SitePointWeatherWeatherServiceInterface');
        $weatherServiceMock->shouldReceive('getTempCelsius')->once()->andReturn(25);

        $temperatureService = new TemperatureService($weatherServiceMock);
        $this->assertEquals(77, $temperatureService->getTempFahrenheit());
    }

}

We start off by creating the mock object. We tell Mockery which object (or interface) we want to mock. The second step is to describe which method will be called on this mock object. Within the shouldReceive() method, we define the name of the method that will be called.

We define how many times this method will be called. We can use once(), twice(), and times(X). In this case, we expect it will only be called once. If it’s not called or called too many times, the unit test will fail.

Finally we define in the andReturn() method, what the value is that will be returned. In this case, we are returning 25. Mockery also has return methods like andReturnNull(), andReturnSelf() and andReturnUndefined(). Mockery is also capable of throwing back exceptions if that is what you expect.

We have our mock object now and can create our TemperatureService object and do a test as usual. 25 Celsius is 77 Fahrenheit, so we check if we receive 77 back from our getTempFahrenheit() method.

If you run vendor/bin/phpunit tests/ within your root, you will get a green light from PHPUnit, indicating everything is perfect.

Advanced

The example above was fairly simple. No parameters, just one simple call. Let’s make things a bit more complicated.

Let’s say our weather service also has a method to get the temperature on an exact hour. We add the following method to our current WeatherServiceInterface.

/**
 * Return the Celsius temperature by hour
 * 
 * @param $hour
 * @return float
 */
public function getTempByHour($hour);

We would like to know, what the average temperature is between 0:00 and 6:00 at night. For that, we create a new method in our TemperatureService which calculates the average temperature. For that, we are retrieving 7 temperatures from our WeatherService and calculating the average.

/**
 * Get average temperature of the night
 * 
 * @return float
 */
public function getAvgNightTemp() {
    $nightHours = array(0, 1, 2, 3, 4, 5, 6);
    $totalTemperature = 0;

    foreach($nightHours as $hour) {
        $totalTemperature += $this->weatherService->getTempByHour($hour);
    }

    return $totalTemperature / count($nightHours);
}

Let’s have a look at our test method.

public function testGetAvgNightTemp() {
    $weatherServiceMock = Mockery::mock('SitePointWeatherWeatherServiceInterface');
    $weatherServiceMock->shouldReceive('getTempByHour')
        ->times(7)
        ->with(Mockery::anyOf(0, 1, 2, 3, 4, 5, 6))
        ->andReturn(14, 13, 12, 11, 12, 12, 13);

    $temperatureService = new TemperatureService($weatherServiceMock);
    $this->assertEquals(12.43, $temperatureService->getAvgNightTemp());
}

Once again we mock the interface and we define the method which will be called. Next, we define how many times this method will be called. We used once() in the previous example, now we are using times(7) to indicate we expect this method to be called 7 times. If the method is not called exactly 7 times, the test will fail. If you didn’t define the listener in the phpunit.xml config file, you wouldn’t receive a notice about this.

Next, we define the with() method. In the with method, you can define the parameters you are expecting. In this case, we are expecting the 7 different hours.

And lastly, we have the andReturn() method. In this case, we indicated the 7 values returned. In case you define fewer return values, the last return value available will be repeated each time.

Of course, Mockery can do a lot more. For a complete guide and documentation, I recommend you take a look at the Github page from mockery.

Conclusion

With PHPUnit, you can already mock objects. However, you can also use Mockery as explained in the examples above. If you are unit testing your classes and you don’t want any other classes affecting your test, mockery can help you out with ease. If you really want to do functional tests, it’s better to have a look if you can integrate the real deal of course.

Leave a Reply

Your email address will not be published. Required fields are marked *