Mockery: A Better Way

Mockery is a PHP extension that offers a superior mocking experience, particularly when compared to PHPUnit. While PHPUnit's mocking framework is powerful, Mockery offers a more natural language with a Hamcrest-like set of matchers. In this article, I'll compare the two mocking frameworks and highlight the best features of Mockery.

Mockery offers a set of mocking-related matchers that are very similar to a Hamcrest dictionary, offering a very natural way to express mocked expectations. Mockery does not override or conflict with PHPUnit's built-in mocking functions; in fact, you can use them both at the same time (and even in the same test method).

Installing Mockery

There are multiple ways to install Mockery; here are the most common methods.

Use Composer

Create a file called composer.json in you project's root folder, and add the following code to that file:

{
"require": {
"Mockery/Mockery": ">=0.7.2"
}
}

Next, simply install Composer in your project's root folder using the following command:

curl -s http://getcomposer.org/installer | php

Finally, install any required dependencies (including Mockery) with this command:

php composer.phar install

With everything installed, let's ensure that our Mockery installation works. For the sake of simplicity, I'll assume that you have a folder, called Test in your project's root directory. All of the examples in this tutorial will reside in this folder. Here's the code I've used to ensure that Mockery works with my project:

The first line ensures that we have access to Mockery. Next, we create a test class, called MockeryVersusPHPUnitGetMockTest, which has a method, testCreateAMockedObject(). The mocked class, AClassToBeMocked, is completely empty at this time; in fact, you could completely remove the class without causing the test to fail.

The testCreateAMockedObject() test method defines two objects. The first is a PHPUnit mock, and the second is created with Mockery. Mockery's syntax is:

$mockedObject = \Mockery::mock('SomeClassToBeMocked');

Assign Simple Expectations

Mocks are commonly used to verify an object's behavior (primarily its methods) by specifying what are called expectations. Let's set up a few simple expectations.

Expect a Method to be Called

Probably the most common expectation is one that expects a specific method call. Most mocking frameworks allow you to specify the amount of calls you expect a method to receive. We'll begin with a simple single call expectation:

This code configures an expectation for both PHPUnit and Mockery. Let's start with the former.

Some Linux distributions make it easy to install Mockery.

We use the expects() method to define an expectation to call someMethod() once. But in order for PHPUnit to work correctly, we must define a class called AClassToBeMocked, and it must have a someMethod() method.

This is a problem. If you are mocking a lot of objects and developing using TDD principles for a top-down design, you would not want to create all the classes and methods before your test. Your test should fail for the right reason, that the expected method was not called, instead of a critical PHP error with no relation to the real implementation. Go ahead and try to remove the someMethod() definition from AClassToBeMocked, and see what happens.

Mockery, on the other hand, allows you to define mocks for classes that do not exist.

Notice that the above example creates a mock for AnInexistentClass, which as its name implies, does not exist (nor does its someMethod() method).

At the end of the above example, we define the SomeClass class to exercise our code. We initialize an object, called $someObject in the first line of the test method, and we effectively exercise the code after defining our expectations.

Please Note: Mockery evaluates expectations on its close() method. For this reason, you should always have a tearDown() method on your test that calls \Mockery::close(). Otherwise, Mockery gives false positives.

Expect More Than One Call

As I noted previously, most mocking frameworks have the ability to specify expectations for multiple method calls. PHPUnit uses the $this->exactly() construct for this purpose. The following code defines the expectations for calling a method multiple times:

Mockery provides two different methods to better suit your needs. The first method, twice(), expects two method calls. The other method is times(), which lets you specify an amount. Mockery's approach is much cleaner and easier to read.

Returning Values

Another common use for mocks is to test a method's return value. Naturally, both PHPUnit and Mockery have the means to verify return values. Once again, let's start with something simple.

Simple Return Values

The following code contains both PHPUnit and Mockery code. I also updated SomeClass to provide a testable return value.

Both PHPUnit's and Mockery's API is straight-forward and easy to use, but I still find Mockery to be cleaner and more readable.

Returning Different Values

Frequent unit testers can testify to complications with methods that return different values. Unfortunately, PHPUnit's limited $this->at($index) method is the only way to return different values from the same method. The following code demonstrates the at() method:

The test still passes. PHPUnit expects two calls to someMethod() that happen inside the tested class when performing the concatenation via the concatenate() method. The first call returns the first value, and the second call returns the second value. But, here's the catch: what would happen if you double the assertion? Here's the code:

PHPUnit continues counting between distinct calls to concatenate(). By the time the second call in the last assertion occurs, $index is at the values 2 and 3. You can make the test pass by modifying your expectations to consider the two new steps, like this:

This method behaves similarly to concatenate(), but it concatenates the string values with " - " as opposed to a single space. Because these two methods perform similar tasks, it makes sense to to test them inside the same test method to avoid duplicate testing.

As demonstrated in the above code, the second function uses a different mocked method called anotherMethod(). I made this change to force us to mock both methods in our tests. Our mockable class now looks like this:

Return Values Based on Given Parameter

Honestly, this is something PHPUnit simply cannot do. At the time of this writing, PHPUnit does not permit you to return different values from the same function based on the function's parameter. Therefore, the following test fails:

Please ignore the fact that there is no logic in this example; it would fail even if it was present. This code, however, does help illustrate the idea.

This test fails because PHPUnit cannot differentiate between the two expectations in the test. The second expectation, expecting parameter 3, simply overrides the first expecting parameter 2. If you attempt to run this test, you get the following error:

Expectation failed for method name is equal to <string:getNumber> when invoked zero or more times
Parameter 0 for invocation AClassToBeMocked::getNumber(2) does not match expected value.
Failed asserting that 2 matches expected 3.

Mockery can do this, and the code below works exactly as you would expect it to work. The method returns different values based on its provided parameters:

This Calculator class has three methods: add(), subtract(), and multiply(). Multiply uses a loop to perform the multiplication by calling the add() for a specified amount of times (e.g. 2 x 3 is really 2 + 2 + 2).

Let's assume that we want to test multiply() in total isolation; so, we'll mock add() and check for specific behavior on multiply(). Here are some possible tests:

The first PHPUnit test is anemic; it simply tests that the method add() is called twice and it returns the final value on each call. It gets the job done, but it's also a little complicated. PHPUnit forces you to pass the list of methods that you want to mock as second parameter to $this->getMock(). Otherwise, PHPUnit would mock all methods, each returning NULL by default. This list must be kept in concordance with the expectations you define on your mocked object.

For example, if I add a second expectation to $phpMock's substract() method, PHPUnit would ignore it and call the original substract() method. That is, unless I explicitly specify the name of the method (substract) in the $this->getmock() statement.

Of course, Mockery is different by allowing you to provide a real object to \Mockery::mock(), and it automatically creates a partial mock. It achieves this by implementing a proxy-like solution for mocking. All the expectations you define are used, but Mockery falls back to the original method if you do not specify an expectation for that method.

Please Note: Mockery's approach is very simple, but internal method calls do not pass through the mocked object.

This example is misleading, but it illustrates how not to use Mockery's partial mocks. Yes, Mockery creates a partial mock if you pass a real object, but it only mocks only external calls. For example, based on the previous code, the multiply() method calls the real add() method. Go ahead and try to change the last expectation from ...->andReturn(6) to ...->andReturn(7). The test should obviously fail, but it doesn't because the real add() executes instead of the mocked add() method.

While syntactically different, the concept is similar to PHPUnit's approach: you have to list the mocked methods in two places. But for any other test, you can just simply pass the real object, which is much easier--especially when dealing with constructor parameters.

Dealing with Constructor Parameters

Let's add a constructor with two parameters to the Calculator class. The revised code:

Every test in this article will fail after adding this constructor. More precisely, the testPartialMock() test results in the following error:

Missing argument 1 for Calculator::__construct(),
called in /usr/share/php/PHPUnit/Framework/MockObject/Generator.php
on line 224 and defined

PHPUnit tries to mock the real object by automatically calling the constructor, expecting to have the parameters correctly set. There are two ways around this problem: either set the parameters, or don't call the constructor.

Mockery automagically works around this problem. It's okay not to specify a constructor parameter; Mockery will simply not call the constructor. But you can specify a list of constructor parameters for Mockery to use. For example:

Technical Considerations

Mockery is another library that integrates your tests, and you may want to consider what technical implications this may have.

Mockery uses a lot of memory. You will have to increase the maximum memory to 512MB if you want to run many tests(say over 1000 tests with more than 3000 assertions). See php.ini documentation for further details.

You have to organize your tests to run in separate processes, when mocking static methods and static method calls.

You can auto-load Mockery into every test by using PHPUnit's bootstrap functionality (helpful when you have many tests and you don't want to repeat yourself).

You can automate the call to \Mockery::close() in each test's tearDown() by editing phpunit.xml.

Final Conclusions

PHPUnit certainly has its issues, especially when it comes to functionality and expressiveness. Mockery can greatly improve your mocking experience by making your tests easy to write and understand - but it's not perfect (there's no such thing!).

This tutorial has highlighted many key aspects of Mockery, but, honestly, we've barely scratched the surface. Be sure to explore the project's Github repository to learn more.

I had my first contact with computers in the mid-80s when I visited my father at work. Probably it was an important moment for what I am doing now. I am a proud member of an agile team working for a company called Syneto.
Through my carrier I programmed in several programming languages and I had the chance to learn and use daily all the major Agile techniques from Scrum to Lean and from TDD to DDD.
Since August 2012 I am sharing my knowledge with the Nettuts+ readers by articles, tutorials and premium courses, all about programming.