Mock your Test Dependencies with Mockery

Share this article

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="\Mockery\Adapter\Phpunit\TestListener"
                  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 SitePoint\Weather.

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 SitePoint\Weather;

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 SitePoint\Weather;

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 at how the method would look when it’s completely finished.

namespace SitePoint\Weather\Tests;

use SitePoint\Weather\TemperatureService;

class TemperatureServiceTest extends \PHPUnit_Framework_TestCase 
{

    public function testGetTempFahrenheit() {
        $weatherServiceMock = \Mockery::mock('SitePoint\Weather\WeatherServiceInterface');
        $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('SitePoint\Weather\WeatherServiceInterface');
    $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.

If you are interested in the code from the project above, you can take a look on this Github page.

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. Are you currently using PHPUnit mocking and are thinking about switching to Mockery? Would you like to see more and bigger examples of Mockery in a follow up article? Let me know in the comments below.

Frequently Asked Questions (FAQs) about Mockery and Test Dependencies

What is Mockery and why is it important in PHP testing?

Mockery is a powerful and flexible PHP mock object framework used in unit testing. It’s designed as a drop-in alternative to PHPUnit’s mock object functionality. Mockery allows developers to isolate code under test and create test doubles, which mimic the behavior of complex objects. This is crucial in unit testing as it ensures that the code under test is not dependent on any external factors or states.

How do I install and set up Mockery in my PHP project?

To install Mockery, you need to have Composer, a dependency manager for PHP. You can install Mockery by running the command composer require --dev mockery/mockery. After installation, you can set up Mockery in your test file by calling \Mockery::close() in your test tear down method to clean up the Mock objects.

How can I create a mock object using Mockery?

Creating a mock object in Mockery is straightforward. You can use the mock() method to create a mock object. For instance, $mock = \Mockery::mock('MyClass'); will create a mock object of MyClass.

How do I define expectations in Mockery?

In Mockery, you define expectations by chaining methods onto the mock object. For example, $mock->shouldReceive('myMethod')->once()->andReturn('mocked value'); This code tells Mockery to expect that ‘myMethod’ will be called once and should return ‘mocked value’.

What is the difference between a mock and a stub in Mockery?

In Mockery, a mock is an object that we can set expectations on, while a stub is a mock object pre-programmed with responses. Stubs are typically used when the response is all that matters, while mocks are used when the interaction itself is being tested.

How can I test private methods with Mockery?

Testing private methods directly is not recommended as it breaks the principle of encapsulation. However, if you need to, you can use the shouldAllowMockingProtectedMethods() method in Mockery to allow mocking of protected and private methods.

How can I handle constructor arguments in Mockery?

If the class you’re mocking has a constructor with arguments, you can pass them as an array to the mock() method. For example, $mock = \Mockery::mock('MyClass', [$arg1, $arg2]); will pass $arg1 and $arg2 to MyClass’s constructor.

How can I mock static methods with Mockery?

Mockery provides a way to mock static methods using the alias: prefix. For example, $mock = \Mockery::mock('alias:MyClass'); will create a mock that can be used to set expectations on MyClass’s static methods.

How can I verify that all expectations have been met in Mockery?

You can use the \Mockery::close() method in your test tear down method to verify that all expectations have been met. If any expectation is not met, Mockery will throw an exception.

How can I handle exceptions in Mockery?

You can set up your mock to throw exceptions using the andThrow() method. For example, $mock->shouldReceive('myMethod')->andThrow(new Exception); will throw an exception when ‘myMethod’ is called.

Peter NijssenPeter Nijssen
View Author

Peter is a software architect from the Netherlands. He freelanced for more then 6 years as a web developer, and meanwhile, he graduated as software engineer with honors. He decided to join CMNTY Corporation which specializes in creating community software and is now responsible for the ongoing development of multiple web applications as well as mobile applications. Peter believes a real developer is able to combine multiple techniques together to make sure the user receives the ultimate experience and enjoys using the application. In his free time, he loves to play board games with anyone who is interested. He especially has a passion for cooperative board games.

dependency injectiondimockmockerymockingOOPHPPHPphpunittddTesting
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week