Mock your Test Dependencies with Mockery

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.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Mohd. Mahabubul ALam

    Im new to testing and I am trying to create a unit test that
    covers the first if statement in the NewsCreator create method. This
    question has two parts really. how should i be instantiating NewsCreator
    to handle the mocked validator and repository, and secondly, what would
    the correct way to test this path be?

    here is my controller method that calls the class that needs testing:

    public function store()
    {
    $creator = new NewsCreator($this);
    return $creator->create(Input::all());
    }

    here is the class that i wish to test, NewsCreator

    listener = $listener;
    $this->repository = $repository;
    $this->validator = $validator;
    $this->errors = [];
    }
    public function create($data)
    {
    if($this->validator->fails($data))
    {
    return $this->listener->newsCreationFails($this->validator->messages());
    }
    if($this->repository->create($data))
    {
    return $this->listener->newsCreationSucceeds();
    }
    return $this->listener->newsCreationFails($this->errors);
    }
    }

    this is the test i attempted to write, but it fails
    with exception
    2) WHSPortalTestsNewsNewsCreatorTest::test_failed_validation
    MockeryExceptionInvalidCountException: Method fails(“foo”) from
    Mockery_1_WHS_Portal_News_NewsValidator should be called exactly 1 times
    but called 0 times.

    makePartial();

    $newsValidator->shouldReceive(‘fails’)->with(‘foo’)->once()->andReturn(true);
    $listener->shouldReceive(‘newsCreationFails’)->once()->with(‘foo’)->andReturn();

    $newsCreator = new WHSPortalNewsNewsCreator($listener,$newsRepo,$newsValidator);
    $newsCreator->create([]);
    }
    }

    updated test

    use TestCase;
    use Mockery as m;

    class NewsCreatorTest extends TestCase {

    public function tearDown()
    {
    m::close();
    }

    public function test_failed_validation()
    {
    $newsRepo = m::mock(‘WHSPortalNewsDbNewsRepository["create"]‘);
    $newsValidator = m::mock(‘WHSPortalNewsNewsValidator["fails"]‘);

    $listener = m::mock(‘WHSPortalTestsNewsNewsListenerStub["newsCreationFails"]‘);

    $newsValidator->shouldReceive(‘fails’)->with([])->once()->andReturn(true);
    $newsValidator->shouldReceive(‘messages’)->once();
    $listener->shouldReceive(‘newsCreationFails’)->once()->with(‘foo’)->andReturn(‘foo-bar’);

    $newsCreator = new WHSPortalNewsNewsCreator($listener,$newsRepo,$newsValidator);

    $result = $newsCreator->create([]);
    $this->assertEquals(‘foo-bar’, $result);
    }
    }

    the stub class

    class NewsListenerStub
    {
    public function newsCreationFails($data)
    {
    return $data;
    }
    }

    php unit-testing testing laravel mockery

    share|improve this question

    edited Feb 16 at 11:11

    asked Feb 16 at 5:21

    Wing5wong

    335

    add comment

    1 Answer

    active
    oldest
    votes

    up vote
    1
    down vote

    accepted

    The method fails() is called with the $data argument in your class.
    In your unit test your are passing in an empty array as data create([]).
    Your argument expectation on the validatorMock is expecting fails() to be called with the parameter foo. You have to alter that to match the empty array.

    $newsValidator->shouldReceive(‘fails’)->with([])->once()->andReturn(true);

    Also you have to specify the validator->messages() method on the validatorMock because that is also being called in your class.

    $newsValidator->shouldReceive(‘messages’)->once();

    For this test to really make sence you have to assert that the result of NewsCreationFails matches the return value of create().

    $listener->shouldReceive(‘newsCreationFails’)->once()->with(‘foo’)->andReturn(‘foo-bar’);

    $result = $newsCreator->create([]);

    $this->assertEquals(‘foo-bar’, $result);

    • https://www.peternijssen.nl/ Peter Nijssen

      It’s hard for me to read your whole post, due to the fact the code is not colored/indented. Am I correct it’s a copy-paste from stackoverflow or similar website? Else point me to that topic :)