The eleventh door of our Advent Calendar!
In the first part of this 2-part series, we saw how unit tests are, at their core, a very simple concept that does not require complex frameworks or bloated libraries, it is all about executing some code and get a feedback. We even saw how a tweet-sized function can be used to write all the unit tests we might need.
After that, we analyzed both the convenience and the problems of running unit tests for WordPress plugins without loading WordPress, and then saw how stubs can allow us to do it, but they have some issues: they need to be written, they offer surface to bugs, they need to be maintained.
In this second part we will see a different approach and tools to that can help us overcome this issues.
If you missed the first article on December 9th, please go read the first part first, because we will refer to concepts and code sample introduced there.
Mock Around The Clock
In the first part we said that what’s matter when testing plugins is not to test that the WordPress code work (it is assumed to work), but to test that custom code uses WordPress functions in the proper way.
For that purpose we seen a couple of examples where thanks to stubs we were able to run tests of WordPress plugin code without loading WordPress.
But… did we actually test that WordPress functions were used correctly?
For example, did we test what happesn if they were called more than once or not called at all? Did we were able to test the arguments that were passed to those WordPress functions?
The stub we wrote were very simple (on purpose) and not capable to catch this sort of improper usages of WordPress code, and that means that when the our code would be ran in production with WordPress being loaded, there’s the possibility of some breakage.
What we would need is a stub that makes the test fail if the function which is expected to be called is not actually called, or it’s called a wrong number of times, or it’s called with wrong arguments. This would (to some degree) guarantee that, if the tests pass, when replacing the stub with the real thing everything works.
I just described a mock object. In facts, that’s an object that implements the same interface(s) of the original object, and allows to test that the method calls it receives are compliant to a set of expectations written to ensure compliance with the original object.
Often times, mock objects are not stored in the filesystem, they are “virtual” objects created on runtime with the help of some library, based on given expectations.
Yesterday, in first part of the article we wrote an example of a
Clock object, and we saw how to use a stub to test it. Now let’s see how a mock object could be used instead:
A PHP library called Mockery is used above to create a mock of the same
Clock interface we saw yesterday.
The mock object does the same job of the
ClockStub we saw, but:
- no need of an additional class be present in the codebase: the mock object is created just-in-time inside the tests itself.
- The code necessary is far less (22 lines VS 4), and offers pretty much zero surface to bugs. In the 4 lines of code it requires, all the code belongs to the Mockery library, which is tested separately.
- The mock is more effective than the stub, because it not only responds to method calls in a pre-defined way, but is also capable to ensure that the methods are called a specific number of times with specific number and type of arguments.
I used Mockery because I like its DSL that reads as plain English and the fact that it can be used with any test framework (even with our tiny TestFrameworkInATweet introduced yesterday), but it is not the only PHP library that allows to create mock objects.
A technical note: Mockery, which will be used for the rest of the article, requires that
Mockery::close() is called at the end of each test. For this reason the rest of the article I will refer to the following file:
that extends the TestFrameworkInATweet introduced yesterday with Mockery features. Thanks to it we can now write tests like this:
and the output of test above should be:
✘ It should work with Mockery, which means this test should fail. FAIL in: /path/to/test-file.php #9. Method test(<Any Arguments>) from Mockery_0 should be called exactly 1 times but called 0 times.
A Lot To Mock in WordPress
Mockery (and other PHP mocking libraries) allow to mock objects, and this is a good help if we want to test WordPress plugins without loading WordPress.
Last time I checked, around 51% of the more than 330000 lines of WordPress PHP code (and libraries it embeds) are written into 368 classes (and 6 interfaces).
Let’s see an example on how with Mockery and our just improved test framework we can test WordPress code that uses objects.
First, some WordPress code:
This is a class that wraps
WP_Query and pass specific arguments to query a “product” post type.
The class is all about calling
WP_Query methods. And we don’t want to test
WP_Query, but we want to test that
WP_Query methods are called properly and that the output of calling our class methods matches the expectations.
Let’s do it:
Thanks to Mockery, without loading WordPress code above tests that the our class returns values according to what
WP_Query returns, and that
WP_Query methods are called exactly how they are expected to be called.
I suggests you to have a look at Mockery documentation because it has a lot of features I’m not going to cover in this article.
By the way, it’s nice, isn’t it?
Yes, but… if it’s true that WordPress has few hundreds of classes, it has more then 2900 functions and without an effective way to mock/stub them, no way we can test WordPress plugins code without loading WordPress.
The Clever Monkey
Mock objects, works for… objects, but we WordPress developers need something similar for functions.
We saw in the first part of the article that when WordPress functions are not defined, it is quite easy to write stubs for them. However, when we write tests in real world, we want to write different stubs based on what we want to test, but PHP does not allow to re-define functions.
It appears clear that what we need is:
- a way to apply the same concepts of objects mocks to functions.
- a way to re-define functions.
The first point is pretty simple to obtain. A function is not that different from an object with a single method. So we just need some code that takes the name of the function we want replace, creates an object for it with a single method, then adds Mockery expectations to that object.
The second point is quite harder. Since 2010 it is available for PHP an open source library called Patchwork that allows to re-define PHP functions on runtime (runtime re-definition of functions, is sometimes referred as “monkey patching”).
Even if PHP does not allow to monkey-patch functions source code, Patchwork uses a trick (I save you the tech details) that makes possible to re-define PHP functions.
So now we have all the ingredients that might help in test WordPress functions without loading WordPress, we just need to “cook” them together.
Guess what, someone already did.
Almost three years ago I started developing Brain Monkey which started as a single class of around 100 lines of code. Brain Monkey at its core do pretty much what I described above: it provides a “bridge” between the function redefinition of Patchwork and the mock creation of Mockery.
Brain Monkey documentation tells us that we need to do some setup to use it: we need to call
Brain\Monkey\setUp() before each test, and
Brain\Monkey\tearDown() after each test.
Brain Monkey uses Mockery, so its
tearDown() function already calls
Because I want to show you how to use Brain Monkey I’m going to write few lines of code to extend our micro test framework with Brain Monkey capabilities:
Let’s see an example of real world WordPress code that we will test soon. It is class that manages the registration and the rendering of a shortcode:
Like any average WordPress code, there are a lot of function calls.
For some of them, e.g.
wp_reset_postdata, we are probably interested only in the fact that the function is defined. For such functions, if we would write the stub “by hand” it would be something like:
just enough to not cause a fatal error because of undefined function.
For other functions, like
esc_html, for testing purposes we would just to return the first argument they receive unchanged. The fact that those functions actually escape values is not something that we should test in our plugin (we assume they work). For those functions the “by hand” stub would look like:
Then we have functions for which we don’t really need to control how they are called (or how many times), but we need control on the return value. For example:
And finally, there are functions for which we need full control: if, when, how and how many times they are called.
For example, for
add_shortcode we probably want to check that is actually called once, that the arguments are the right ones and called in proper order. The same for
shortcode_atts, because we want to check that we pass arguments in the right order and that the 3rd argument matches the 1st argument
We will see soon how Brain Monkey will help us to obtain what we want without writing by hand any stub.
Regarding objects, the class we are going to test uses a single instance of
WP_Query, but this time instead of having an instance of it accepted in constructor,
WP_Query is instantiated directly inside the render method.
Normally, I would say this is a bad practice. The problem here is that WordPress classes, more often than not, does not really follow good OOP principles. For example,
WP_Query accepts query arguments in the constructor and a database query is performed as soon as the object is created. For that reason it is quite common to have
WP_Query instances created inside other classes, just before they are used.
How can we possibly test this thing? Mockery has a feature for us. Thanks to its
overload feature, Mockery allow us to intercept calls to
new keyword and replace the class being instantiated with a mock object.
Let’s try it out:
We tested successfully a real word WordPress plugin class without loading WordPress.
Surely, the test is quite big!
The limited features of out micro test framework don’t help here.
If we were using a more advanced framework we could have a much smaller test: we could extract all the functions stubbing to a separate test class method. Same for query overloading. Finally, instead of performing all the assertions at once, we could have used different tests.
Moreover, we need to manage output buffer by hand, whereas advanced frameworks have this feature embedded.
To give you an idea, in PHPUnit a test for the same class could be something like this:
Do you like Brain Monkey so far? There’s more about it!
Hooked on Testing
With Mockery providing all the methods to mock and stub objects, and Brain Monkey bringing those features to functions we have everything we need to test WordPress plugin without loading WordPress.
however, there are a set of functions that we find everywhere in WordPress plugins, and mock them every time is quite tedious and awkward. I’m talking about the plugin API functions.
Ensure an action or a filter has been added and with which callable, testing that an action as been fired and with which arguments, or that a filter as been applied and maybe respond to it… these are all operations that testing real world plugins we will feel the the need to do many and many times.
Sure we can use Brain Monkey
Functions\expect to create function stubs for
apply_filters, and all the other functions of plugins API, but that would require a lot of bootstrap code for each test, reducing (or nullifying) the convenience of running test without loading WordPress.
Luckily, Brain Monkey has an API designed just to test WordPress plugins API.
The first good news is that Brain Monkey defines already all the functions of plugins API in a way that is 100% compatible WordPress real code.
That means that if the plugin code we want to test uses any of the functions of the plugin API, we could test it without mocking or stubbing anything.
This looks like some real world WordPress code.
Thanks to Brain Monkey we could do something like this:
We ran our tests just like WordPress was loaded, without any mock or stub, and everything worked as expected!
The function under test uses 4 functions of plugins API (
add_action) and we even used
do_action it tests code… and everything just worked.
Even if this is amazing already (sorry, I’m biased) the fact we can easily set expectations on those hooks or even respond to filters, is even better.
Some other tests we can write:
In short: we will be able to test all the plugin API functions with the finest level of control, and with a syntax that is very readable and the consistent with the one we use to create mock objects with Mockery.
Before ending the article, I want to left here that plugin API functions are not the only functions that are already mocked by Brain Monkey for you.
If your code use any of the following functions:
You don’t need to mock them, or anything… they will just work just like they do in WordPress context.
In short, thanks to Mockery and Brain Monkey we have the possibility to write unit tests for our plugin without loading WordPress in a way that is very easy to start with and produces quite maintainable tests.
Do you have questions? Feedback? Do you think I missed something? Do you think I am completely wrong in something or anything? The comment section is here to serve you, and we are listening…
PS: Brain Monkey is open source and very open to contributions.
And tomorrow we have another great contribution to our Inpsyde Advent Calendar!