It's an unfortunate truth that, while the basic principle behind testing is quite simple, fully introducing this process into your day-to-day coding workflow is more difficult than you might hope. The various jargon alone can prove overwhelming! Luckily, a variety of tools have your back, and help to make the process as simple as it can be. Mockery, the premier mock object framework for PHP, is one such tool!
In this article, we'll dig into what mocking is, why it's useful, and how to integrate Mockery into your testing workflow.
Mocking Decoded
A mock object is nothing more than a bit of test jargon that refers to simulating the behavior of real objects. In simpler terms, often, when testing, you won't want to execute a particular method. Instead, you simply need to ensure that it was, in fact, called.
Perhaps an example is in order. Imagine that your code triggers a method that will log a bit of data to a file. When testing this logic, you certainly don't want to physically touch the file system. This has the potential to drastically decrease the speed of your tests. In these situations, it's best to mock your file system class, and, rather than manually read the file to prove that it was updated, merely ensure that the applicable method on the class was, in fact, called. This is mocking! There's nothing more to it than that; simulate the behavior of objects.
Remember: jargon is just jargon. Never allow a confusing piece of terminology to deter you from learning a new skill.
Particularly as your development process matures - including embracing the single responsibility principle and leveraging dependency injection - a familiarity with mocking will quickly become essential.
Mocks vs. Stubs: Chances are high that you'll often hear the terms, mock and stub, thrown about interchangably. In fact, the two serve different purposes. The former refers to the process of defining expectations and ensuring desired behavior. In other words, a mock can potentially lead to a failed test. A stub, on the other hand, is simply a dummy set of data that can be passed around to meet certain criteria.
The defacto testing library for PHP, PHPUnit, ships with its own API for mocking objects; however, unfortunately, it can prove cumbersome to work with. As you're surely aware, the more difficult testing is, the more likely it is that the developer simply (and sadly) won't.
Luckily, a variety of third-party solutions are available through Packagist (Composer's package repository), which allow for increased readability, and, more importantly, writeability. Among these solutions - and most notable of the set - is Mockery, a framework-agnostic mock object framework.
Designed as a drop-in alternative for those who are overwhelmed by PHPUnit's mocking verbosity, Mockery is a simple, but powerful utility. As you'll surely find, in fact, it's the industry standard for modern PHP development.
Installation
Like most PHP tools these days, the recommended method to install Mockery is through Composer (though it's available through Pear too).
Wait, what's this Composer thing? It's the PHP community's preferred tool for dependency management. It provides an easy way to declare a project's dependencies, and pull them in with a single command. As a modern PHP developer, it's vital that you have a basic understanding of what Composer is, and how to use it.
If working along, for learning purposes, add a new composer.json
file to an empty project and append:
{ "require-dev": { "mockery/mockery": "dev-master" } }
This bit of JSON specifies that, for development, your application requires the Mockery library. From the command-line, a composer install --dev
will pull in the package.
$ composer install --dev Loading composer repositories with package information Installing dependencies (including require-dev) - Installing mockery/mockery (dev-master 5a71299) Cloning 5a712994e1e3ee604b0d355d1af342172c6f475f Writing lock file Generating autoload files
As an added bonus, Composer ships with its own autoloader for free! Either specify a classmap of directories and
composer dump-autoload
, or follow the PSR-0 standard and adjust your directory structure to match. Refer to Nettuts+ to learn more. If you're still manually requiring countless files in each PHP file, well, you just might be doing it wrong.
The Dilemma
Before we can implement a solution, it's best to first review the problem. Imagine that you need to implement a system for handling the process of generating content and writing it to a file. Perhaps the generator compiles various data, either from local file stubs, or a web service, and then that data is written to the file system.
If following the single responsibility principle - which dictates that each class should be responsible for exactly one thing - then it stands to reason that we should split this logic into two classes: one for generating the necessary content, and another for physically writing the data to a file. A Generator
and File
class, respectively, should do the trick.
Tip: Why not use
file_put_contents
directly from theGenerator
class? Well, ask yourself: "How could I test this?" There are techniques, such as monkey patching, which can allow you to overload these sorts of things, but, as a best practice, it's better to instead wrap such functionality up, so that it may easily be mocked with tools, like Mockery!
Here's a basic structure (with a healthy dose of pseudo code) for our Generator
class.
<?php // src/Generator.php class Generator { protected $file; public function __construct(File $file) { $this->file = $file; } protected function getContent() { // simplified for demo return 'foo bar'; } public function fire() { $content = $this->getContent(); $this->file->put('foo.txt', $content); } }
Dependency Injection
This code leverages what we refer to as dependency injection. Once again, this is simply developer jargon for injecting a class's dependencies through its constructor method, rather than hard-coding them.
Why is this beneficial? Because, otherwise, we wouldn't be able to mock the File
class! Sure, we could mock the File
class, but if its instantiation is hard-coded into the class that we're testing, there's no easy way to replace that instance with the mocked version.
public function __construct() { // anti-pattern $this->file = new File; }
The best way to build testable application is to approach each new method call with the question, "How might I test this?" While there are tricks for getting around this hard-coding, doing so is widely considered to be a bad practice. Instead, always inject a class's dependencies through the constructor, or via setter injection.
Setter injection is more or less identical to constructor injection. The principle is exactly the same; the only difference is that, rather injecting the class's dependencies through its constructor method, they're instead done so through a setter method, like so:
public function setFile(File $file) { $this->file = $file; }
A common criticism of dependency injection is that it introduces additional complexity into an application, all for the sake of making it more testable. Though the complexity argument is debatable in this author's opinion, if you should prefer, you can allow for dependency injection, while still specifying fallback defaults. Here's an example:
class Generator { public function __construct(File $file = null) { $this->file = $file ?: new File; } }
Now, if an instance of File
is passed through to the constructor, that object will be used in the class. On the other hand, if nothing is passed, the Generator
will fall back to manually instantiating the applicable class. This allows for such variations as:
# Class instantiates File new Generator; # Inject File new Generator(new File); # Inject a mock of File for testing new Generator($mockedFile);
Continuing on, for the purposes of this tutorial, the File
class will be nothing more than a simple wrapper around PHP's file_put_contents
function.
<?php // src/File.php class File { /** * Write data to a given file * * @param string $path * @param string $content * @return mixed */ public function put($path, $content) { return file_put_contents($path, $content); } }
Rather simple, eh? Let's write a test to see, first-hand, what the problem is.
<?php // tests/GeneratorTest.php class GeneratorTest extends PHPUnit_Framework_TestCase { public function testItWorks() { $file = new File; $generator = new Generator($file); $generator->fire(); } }
Please note that these examples assume that the necessary classes are being autoloaded with Composer. Your
composer.json
file optionally accepts anautoload
object, where you may specify which directories or classes to autoload. No more messyrequire
statements!
If working along, running phpunit
will return:
OK (1 test, 0 assertions)
It's green; that means we can move on to the next task, right? Well, not exactly. While it's true that the code does, indeed, work, each time this test is run, a foo.txt
file will be created on the file system. What about when you've written dozens more tests? As you can imagine, very quickly, your test's speed of execution will stutter.
Still not convinced? If reduced testing speed won't sway you, then consider common sense. Think about it: we're testing the Generator
class; why do we have any interest in executing code from the File
class? It should have its own tests! Why the heck would we double up?
The Solution
Hopefully, the previous section provided the perfect illustration for why mocking is essential. As was noted earlier, though we could make use of PHPUnit's native API to serve our mocking requirements, it's not overly enjoyable to work with. To illustrate this truth, here's an example for asserting that a mocked object should receive a method, getName
and return John Doe
.
public function testNativeMocks() { $mock = $this->getMock('SomeClass'); $mock->expects($this->once()) ->method('getName') ->will($this->returnValue('John Doe')); }
While it gets the job done - asserting that a getName
method is called once, and returns John Doe - PHPUnit's implementation is confusing and verbose. With Mockery, we can drastically improve its readability.
public function testMockery() { $mock = Mockery::mock('SomeClass'); $mock->shouldReceive('getName') ->once() ->andReturn('John Doe'); }
Notice how the latter example reads (and speaks) better.
Continuing with the example from the previous "Dilemma section, this time, within the GeneratorTest
class, let's instead mock - or simulate the behavior of - the File
class with Mockery. Here's the updated code:
<?php class GeneratorTest extends PHPUnit_Framework_TestCase { public function tearDown() { Mockery::close(); } public function testItWorks() { $mockedFile = Mockery::mock('File'); $mockedFile->shouldReceive('put') ->with('foo.txt', 'foo bar') ->once(); $generator = new Generator($mockedFile); $generator->fire(); } }
Confused by the
Mockery::close()
reference within thetearDown
method? This static call cleans up the Mockery container used by the current test, and run any verification tasks needed for your expectations.
A class may be mocked using the readable Mockery::mock()
method. Next, you'll typically need to specify which methods on this mock object you expect to be called, along with any applicable arguments. This may be accomplished, via the shouldReceive(METHOD)
and with(ARG)
methods.
In this case, when we call $generate->fire()
, we're asserting that it should call the put
method on the File
instance, and send it the path, foo.txt
, and the data, foo bar
.
// libraries/Generator.php public function fire() { $content = $this->getContent(); $this->file->put('foo.txt', $content); }
Because we're using dependency injection, it's now a cinch to instead inject the mocked File
object.
$generator = new Generator($mockedFile);
If we run the tests again, they'll still return green, however, the File
class - and, consequently, the file system - will never be touched! Again, there's no need to touch File
. It should have its own tests! Mocking for the win!
Simple Mock Objects
Mock objects needn't always reference a class. If you only require a simple object, perhaps for a user, you might pass an array to the mock
method - where, for each item, the key and value correspond to the method name and return value, respectively.
public function testSimpleMocks() { $user = Mockery::mock(['getFullName' => 'Jeffrey Way']); $user->getFullName(); // Jeffrey Way }
Return Values From Mocked Methods
There will surely be times, when a mocked class method needs to return a value. Continuing on with our Generator/File example, what if we need to ensure that, if the file already exists, it shouldn't be overwritten? How might we accomplish that?
The key is to use the andReturn()
method on your mocked object to simulate different states. Here's an updated example:
public function testDoesNotOverwriteFile() { $mockedFile = Mockery::mock('File'); $mockedFile->shouldReceive('exists') ->once() ->andReturn(true); $mockedFile->shouldReceive('put') ->never(); $generator = new Generator($mockedFile); $generator->fire(); }
This updated code now asserts that an exists
method should be triggered on the mocked File
class, and it should, for the purposes of this test's path, return true
, signaling that the file already exists and shouldn't be overwritten. We next ensure that, in situations such as this, the put
method on the File
class is never triggered. With Mockery, this is easy, thanks to the never()
expectation.
$mockedFile->shouldReceive('put') ->never();
Should we run the tests again, an error will be returned:
Method exists() from File should be called exactly 1 times but called 0 times.
Aha; so the test expected that $this->file->exists()
should be called, but that never happened. As such, it failed. Let's fix it!
<?php class Generator { protected $file; public function __construct(File $file) { $this->file = $file; } protected function getContent() { // simplified for demo return 'foo bar'; } public function fire() { $content = $this->getContent(); $file = 'foo.txt'; if (! $this->file->exists($file)) { $this->file->put($file, $content); } } }
That's all there is to it! Not only have we followed a TDD (test-driven development) cycle, but the tests are back to green!
It's important to remember that this style of testing is only effective if you do, in fact, test the dependencies of your class as well! Otherwise, though the tests may show green, for production, the code will break. Our demo this far has only ensured that
Generator
works as expected. Don't forget to testFile
as well!
Expectations
Let's dig a bit more deeply into Mockery's expectation declarations. You're already familiar with shouldReceive
. Be careful with this, though; its name is a bit misleading. When left on its own, it does not require that the method should be triggered; the default is zero or more times (zeroOrMoreTimes()
). To assert that you require the method to be called once, or potentially more times, a handful of options are available:
$mock->shouldReceive('method')->once(); $mock->shouldReceive('method')->times(1); $mock->shouldReceive('method')->atLeast()->times(1);
There will be times when additional constraints are necessary. As demonstrated earlier, this can be particularly helpful when you need to ensure that a particular method is triggered with the necessary arguments. It's important to keep in mind that the expectation will only apply if a method is called with these exact arguments.
Here's a few examples.
$mock->shouldReceive('get')->withAnyArgs()->once(); // the default $mock->shouldReceive('get')->with('foo.txt')->once(); $mock->shouldReceive('put')->with('foo.txt', 'foo bar')->once();
This can be extended even further to allow for the argument values to be dynamic in nature, as long as they meet a certain criteria. Perhaps we only wish to ensure that a string is passed to a method:
$mock->shouldReceive('get')->with(Mockery::type('string'))->once();
Or, maybe the argument needs to match a regular expression. Let's assert that any file name that ends with .txt
should be matched.
$mockedFile->shouldReceive('put') ->with('/\.txt$/', Mockery::any()) ->once();
And as a final (but not limited to) example, let's allow for an array of acceptable values, using the anyOf
matcher.
$mockedFile->shouldReceive('get') ->with(Mockery::anyOf('log.txt', 'cache.txt')) ->once();
With this code, the expectation will only apply if the first argument to the get
method is log.txt
or cache.txt
. Otherwise, a Mockery exception will be thrown when the tests are run.
Mockery\Exception\NoMatchingExpectationException: No matching handler found...
Tip: Don't forget, you can always alias
Mockery
asm
at the top of your class to make things a bit more succinct:use Mockery as m;
. This allows for the more succinct,m::mock()
.
Lastly, we have a variety of options for specifying what the mocked method should do or return. Perhaps we only need it to return a boolean. Easy:
$mock->shouldReceive('method') ->once() ->andReturn(false);
Partial Mocks
You may find that there are situations when you only need to mock a single method, rather than the entire object. Let's imagine, for the purposes of this example, that a method on your class references a custom global function (gasp) to fetch a value from a configuration file.
<?php class MyClass { public function getOption($option) { return config($option); } public function fire() { $timeout = $this->getOption('timeout'); // do something with $timeout } }
While there are a few different techniques for mocking global functions. nonetheless, it's best to avoid this method call all together. This is precisely when partial mocks come into play.
public function testPartialMockExample() { $mock = Mockery::mock('MyClass[getOption]'); $mock->shouldReceive('getOption') ->once() ->andReturn(10000); $mock->fire(); }
Notice how we've placed the method to mock within brackets. Should you have multiple methods, simply separate them by a comma, like so:
$mock = Mockery::mock('MyClass[method1, method2]');
With this technique, the remainder of the methods on the object will trigger and behave as they normally would. Keep in mind that you must always declare the behavior of your mocked methods, as we've done above. In this case, when getOption
is called, rather than executing the code within it, we simply return 10000
.
An alternative option is to make use of passive partial mocks, which you can think of as setting a default state for the mock object: all methods defer to the main parent class, unless an expectation is specified.
The previous code snippet may be rewritten as:
public function testPassiveMockExample() { $mock = Mockery::mock('MyClass')->makePartial(); $mock->shouldReceive('getOption') ->once() ->andReturn(10000); $mock->fire(); }
In this example, all methods on MyClass
will behave as they normally would, excluding getOption
, which will be mocked and return 10000`.
Hamcrest
Once you've familiarized yourself with the Mockery API, it's recommended that you also leverage the Hamcrest library, which provides an additional set of matchers for defining readable expectations. Like Mockery, it may be installed through Composer.
"require-dev": { "mockery/mockery": "dev-master", "davedevelopment/hamcrest-php": "dev-master" }
Once installed, you may use a more human-readable notation to define your tests. Below are a handful of examples, including slight variations that achieve the same end result.
<?php class HamCrestTest extends PHPUnit_Framework_TestCase { public function testHamcrestMatchers() { $name = 'Jeffrey'; $age = 28; $hobbies = ['coding', 'guitar', 'chess']; assertThat($name, is('Jeffrey')); assertThat($name, is(not('Joe'))); assertThat($age, is(greaterThan(20))); assertThat($age, greaterThan(20)); assertThat($age, is(integerValue())); assertThat(new Foo, is(anInstanceOf('Foo'))); assertThat($hobbies, is(arrayValue())); assertThat($hobbies, arrayValue()); assertThat($hobbies, hasKey('coding')); } }
Notice how Hamcrest allows you to write your assertions in as readable or terse a way as you desire. The use of the is()
function is nothing more than syntactic sugar to aid in readability.
You'll find that Mockery blends quite nicely with Hamcrest. For instance, with Mockery alone, to specify that a mocked method should be called with a single argument of type, string
, you might write:
$mock->shouldReceive('method') ->with(Mockery::type('string')) ->once();
If using Hamcrest, Mockery::type
may be replaced with stringValue()
, like so:
$mock->shouldReceive('method') ->with(stringValue()) ->once();
Hamcrest follows the resourceValue naming convention for matching the type of a value.
nullValue
integerValue
arrayValue
- rinse and repeat
Alternatively, to match any argument, Mockery::any()
may become anything()
.
$file->shouldReceive('put') ->with('foo.txt', anything()) ->once();
Summary
The biggest hurdle to using Mockery is, ironically, not the API, itself.
The biggest hurdle to using Mockery is, ironically, not the API, itself, but understanding why and when to use mocks in your testing.
The key is to learn and respect the single responsibility principle in your coding workflow. Coined by Bob Martin, the SRP dicates that a class "should have one, and only one, reason to change." In other words, a class shouldn't need to be updated in response to multiple, unrelated changes to your application, such as modifying business logic, or how output is formatted, or how data may be persisted. In its simplest form, just like a method, a class should do one thing.
The File
class manages file system interactions. A MysqlDb
repository persists data. An Email
class prepares and sends emails. Notice how, in none of these example was the word, and, used.
Once this is understood, testing becomes considerably easier. Dependency injection should be used for all operations that do not fall under the class's umbrella. When testing, focus on one class at a time, and mock all of its dependencies. You're not interested in testing them anyways; they have their own tests!
Though nothing prevents you from making use of PHPUnit's native mocking implementation, why bother when Mockery's improved readability is only a composer update
away?
Comments