I’ve lost count of the number of posts I’ve read about unit testing; how to set it up, why you should do it, and what tools to use. It’s something I’ve been pushing myself to do and get better at but I’ve always struggled to translate what I’ve read about testing into writing tests for my own code. However, recently I feel like the principles I’ve been reading about are finally clicking in my brain and I’ve actually been able to successfully write unit tests for my code, including legacy codebases that I had previously thought were untestable.
Recently, I’ve been working on changes to the internal plugin that extends the WooCommerce API on the Delicious Brains site. Previously the plugin had no unit tests and was a large, messy class with all the functionality stored inside it. The plugin allows our premium products to communicate with WooCommerce on the site for licensing and subscriptions and therefore extremely critical to our business. Before making large changes I decided to get some tests written, as well as perform some necessary refactoring. This post will serve as a practical guide to writing unit tests – what to test and how to test it, to refactoring code to allow it to be tested, and the tips I learned along the way.
PHPUnit
There are a few unit testing frameworks out there, but my framework of choice is PHPUnit, which is also used by the WordPress team to test core. Its installation is straightforward, I find the simplest method is using Composer:
composer require --dev phpunit/phpunit
All of our plugins over at Delicious Brains have a suite of unit tests set up using a version of the WP-CLI `scaffold plugin-tests` command. Essentially it configures all the necessary files (and a few more) to run PHPUnit. This command is great to get you started with testing but has actually hampered my understanding of unit testing as a whole.
The command writes a setup script that creates a test installation of WordPress running on a separate database, and uses a bootstrap script to ensure the plugin you are testing is activated. This is unnecessary for unit testing.
We are testing our plugin code only, that code being the system under test (SUT). Unit testing is the practice of testing the smallest parts of our SUT – the code, functions or procedures, but usually this is class methods in an Object-oriented programming (OOP) sense. Although our plugin code is run by WordPress, we don’t need to run WordPress to run our tests. We actually don’t care about code that originates outside of our SUT, and can’t rely on it for testing. That would be more like an integration test.
For example, if our plugin makes a call to the Amazon Web Services API to perform some action and we are writing a test for the method that makes the call, we don’t want to rely on AWS being up every time we run the tests.
I’ll talk about dealing with external code in our tests later on. But for now, I recommend staying clear of the WP-CLI setup approach, and just install and configure PHPUnit very simply. I typically create a “\tests” directory in the plugin root, and add a phpunit.xml config file, and a bootstrap.php file to load our plugin files when the tests are run.
Having a file to autoload you plugin’s files is helpful for the plugin itself, so you don’t have to write `require` statements everywhere. But it also comes in very handy for testing. If you have an autoloader (either using Composer or a custom one), you can simply require it in your testing bootstrap.php file.
That way you don’t need to actually load the plugin itself for every test, which might have unexpected results depending on how you have configured your main plugin file. My `tests/bootstrap.php` file looks like this:
$root_dir = dirname( dirname( __FILE__ ) );
require_once $root_dir . '/vendor/autoload.php'; // Composer autoloader
require_once $root_dir . '/includes/autoload.php'; // Plugin custom autoloader
Writing Unit Tests
You’ve installed PHPUnit and have configured your test suite. Choose one of your plugin’s classes and create a new test case for it. Ok, so what next? How do we choose what to write tests for? Where do we start?
Something that is perhaps obvious to some, but wasn’t clear to me for a long time is that we only want our tests to run public methods of a class. Public methods are the interface to a class, while protected and private methods are implementation details. By the nature of OOP, we can’t write a test that executes a protected or private method of a class instance. But then how can we test the code inside those methods? Make them public? Not a great idea, you would be breaking the OOP structure you put in place originally. Use Reflection to access them? It’s possible, but unnecessary.
So what do we do? We write tests that only call public methods, but we write a number of test variations that exercise any protected or private methods called inside that public method. Here is a simple example (I promise the examples will get more real world!):
plugin-class.php
class Plugin_Class {
public function do_something( $variable ) {
if ( is_wp_error( $this->check_something( $variable ) ) ) {
return false;
}
// Do something
return true;
}
protected function check_something( $variable ) {
if ( empty ( $variable ) ) {
return new \WP_Error( 'missing-variable' );
}
return true;
}
}
test-plugin-class.php
public function test_do_something_with_empty_var() {
$instance = new \Plugin_Class();
$this->assertFalse( $this->instance->do_something( '' ) );
}
public function test_do_somrething_with_var() {
$instance = new \Plugin_Class();
$this->assertTrue( $this->instance->do_something( 'test-va' ) );
}
These two test cases are different scenarios for a call to the same public method, but based on the different input we can test the code execution inside the protected method.
How to Write a Test
Usually unit tests are typically constructed of three stages. As you can see in the test example above, we arrange the thing we are testing, an instance of the `Plugin_Class`, we then get that instance to act by executing the `do_something` method with a certain input, and finally asserting that the result of that method is true.
Unit tests should verify the behavior of the code you’re testing, and the type of test would typically be one of two types:
- State-based testing is verifying that the code returns expected results, or that an expected state is set
- Interaction-based testing is checking that the code correctly invokes certain methods
The majority of the tests I write are state-based, as most of the code I’m testing returns some response that can be checked. But what happens if the method doesn’t return something? How do you ensure all the code is fully tested? One way is to test for interaction by telling PHPUnit to check that certain methods are invoked (more on that later), or you can use code coverage to help you write your tests.
Code Coverage
PHPUnit’s code coverage analysis has really helped me write my tests while developing. It visualizes which lines of your code are being executed by your tests. It’s great to be able to see what lines of a method are untested so you can write a specific unit test to cover the scenario to execute that code, and sometimes catch a bug you didn’t know was there.
Code coverage with PHPUnit requires the Xdebug extension to be installed, but other than that it’s quite simple to get up and running. Running this will generate a set of HTML reports with the analysis for all the files you want included:
cd tests
../vendor/bin/phpunit --coverage-clover=./reports/clover.xml --coverage-html=./reports
The report also has a handy dashboard with graphs to display metrics for classes and methods around code coverage and complexity.
We ask Travis CI to generate the code coverage after a successful run of our tests and send it to Scrutinizer so we can view this for our branches every time plugin code is pushed to GitHub. However, being able to run the code coverage locally while developing is invaluable when refactoring or adding tests to a legacy codebase.
Let’s look at a real example.
Here we have the report showing us a protected method that is responsible for activating a license via the API on deliciousbrains.com. My current tests have exercised most of it but not all:
The code looks like this, albeit somewhat simplified and with only relevant methods:
class Activate {
public function init() {
// Do some stuff
$activation = $this->activate_licence( $license );
if ( is_wp_error( $activation ) ) {
return false
}
return true
}
protected function activate_licence( $is_reactivating = false ) {
global $wc_software;
$site_url = $this->get_var( 'site_url' );
if ( empty( $site_url ) ) {
return new \WP_Error( 'no_site_url_arg' );
}
$site_url = str_replace( array( 'http://', 'https://' ), '', $site_url );
if ( ! $this->activate_licence_key( $this->licence->key_id, $site_url ) ) {
return new \WP_Error( 'activation_failed' );
}
}
protected function activate_licence_key( $key_id, $instance = '', $platform = '' ) {
$activation = $this->get_licence_instance( $key_id, $instance );
if ( $activation ) {
return $this->update_activation( $activation );
}
return $this->add_activation( $key_id, $instance, $platform );
}
protected function get_licence_instance( $key_id, $instance = '' ) {
global $wpdb;
return $wpdb->get_row( $wpdb->prepare( "
SELECT *
FROM {$wpdb->prefix}woocommerce_software_activations
WHERE key_id = %s
AND instance = %s
", $key_id, $instance ) );
}
protected function get_var( $key, $filter = FILTER_DEFAULT ) {
return filter_input( INPUT_GET, $key, $filter );
}
}
These are my existing tests for the public `init` method:
public function test_init_no_site_url() {
$instance = new \Activate()
$this->assertFalse( $instance->init() );
}
public function test_init_failed_activation() {
$instance = \Mockery::mock( ‘\Activate’ )->makePartial();
// Mock the get_var method so we can spoof $_GET['site_url']
$instance->shouldReceive( 'get_var' )->with( 'site_url' )->andReturn( 'http://test.com' );
global $wpdb;
// Mocks the result of the database call in the get_licence_instance() method,
// So it looks like activation can’t be found.
$wpdb = \Mockery::mock( '\WPDB' );
$wpdb->prefix = '';
$wpdb->shouldReceive( 'prepare' )->andReturn( '' );
$wpdb->shouldReceive( 'get_row' )->andReturn( false );
$this->assertFalse( $instance->init() );
}
That just leaves me with a test to write to cover the scenario where the activation for the license already exists, so we can exercise and test the `update_activation()` method.
Hang on, what is all this Mockery and mocking business in the tests?
Good question! Let me explain.
Test Doubles
As I mentioned earlier, we only want to test our code and not have to worry about external code that we might use. This is where Test Doubles come in. There are different types of Test Doubles, including Dummies, Stubs, and Mocks. Adam Brett gives a much better explanation of the differences than I could give.
A Test Double is essentially another object that is like a piece of external code that our code uses. It’s a representation of the Amazon Web Services class, the database abstraction class, or WP_Query and other WordPress classes. We can replace the external with a double and tell it to expect certain parameters and return specific results. This way we can craft our tests to respond to different executions of our code by affecting the external parts of our code, like in the example above where we create a mock of the global $wpdb instance to control the result of the `get_row` method.
I mentioned earlier about interaction-based testing for methods that might not return data we can check. Mocking allows you to test that a method is invoked within the method you are testing. This can be a method of the class of the method you are testing or a method of another dependency.
Mocking Tools and Tips
PHPUnit comes with mocking capability in the form of `getMockBuilder` which allows you to mock a class, override methods and control values. However, the syntax to define the mock with PHPUnit can get a bit unwieldy, and the mocked object can’t be further changed after creation, making it hard to DRY up the mocking code in our test cases.
Mockery
To streamline my testing workflow, I’ve recently been using Mockery, a mocking framework that sits on top of PHPUnit. It makes writing mocked objects super simple, with fluent and succinct syntax.
Let’s look at a quick example of how you would mock a class using PHPUnit and Mockery:
PHPUnit
$instance = $this->getMockBuilder( MyClass::class )->setMethods( [ 'my_method' ] )->getMock();
$instance->expects( $this->any() )->method( 'my_method' )->will( $this->returnValue( true ) );
Mockery
$instance = \Mockery::mock( MyClass::class )->makePartial();
$instance->shouldReceive( 'my_method' )->andReturn( true );
The good thing with Mockery is that it allows you to progressively add to your mock, making it much easier to build your tests. With PHPUnit’s `getMockBuilder()` you need to tell it which methods upfront you want to mock, but with the `makePartial()` function in Mockery you can just tell the class to be a partial mock, and then later define the behavior of certain methods.
Mocking WordPress
As I said before, we don’t need to load WordPress in order to test our plugin code. But what happens if we try to test a method that executes WordPress code? Consider this simple example:
class My_Class {
public function get_my_posts() {
$posts = get_posts();
if ( empty( $posts ) ) {
return false;
}
return $posts;
}
}
public function test_get_my_posts_empty_posts() {
$instance = new My_Class();
$this->assertFalse( $instance->get_my_posts() );
}
When we run the test, we get an error:
`Error: Call to undefined function My_Class\get_posts()`
To get round that we could create a simple double for the `get_posts()` function, but then we would need to do that for every WordPress function our code uses. A much better solution is to use the awesome WP_Mock framework from the folks at 10up:
Ideally, a unit test will not depend on WordPress being loaded in order to test our code. By constructing mocks, it’s possible to simulate WordPress core functionality by defining their expected arguments, responses, the number of times they are called, and more.
It allows you to mock core WordPress functions, as well as handling action and filter hooks so you can assert in your tests if certain hooks have been added (`add_action`, `add_filter`) or applied (`do_action`, `apply_filters`) in your code:
class Router {
public function init() {
add_action( 'woocommerce_api_delicious-brains', array( $this, 'handle_request' ) );
}
public function test_router_init() {
$instance = new \Router();
\WP_Mock::expectActionAdded( woocommerce_api_delicious-brains, array( $instance, ‘'handle_request' ) );
$instance->init();
}
}
WP_Mock doesn’t handle mocking WordPress classes, but as the framework is built on Mockery we can just mock those classes as before. However, when it comes to using WordPress classes in our code, depending on how you write your methods, you might find we come up against hard dependencies.
Hard Dependencies
Hard dependencies are classes are instantiated inside methods making them hard to switch out and therefore harder to test. For example:
class My_Class {
public function get_some_data( $args ) {
$query = new \WP_Query();
$posts = $query->query( $args );
if ( empty( $posts ) ) {
return false;
}
return $posts;
}
}
This happens a lot in legacy codebases due to code architecture and is prevalent within WordPress.
Testing this method is problematic.
Simply creating a mock of the `WP_Query` class in our test will not work, as the code creates a new instance which won’t be our mock. Solving hard dependencies is ultimately a refactoring job which I’ll touch on later, however, for now we can use Mockery’s instance mocks to get round the issue.
By adding the `overload:` prefix to our class name when creating the mock, we tell PHP to load our mocked version of the class first, so any new instantiations will be of that mock. The downside of this is that we need to run the unit test in a separate process to avoid a class already defined error:
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function test_get_my_posts_empty_posts() {
$wp_query = \Mockery::mock( ‘overload:\WP_Query’ );
$wp_query->shouldReceive( ‘query’ )->andReturn( array() )
$instance = new My_Class();
$this->assertFalse( $instance->get_my_posts() );
}
Refactoring For Testability
Sometimes our code just isn’t written in the way that makes testing it easy or even possible. This means we need to do some refactoring to allow us to test and ultimately help make our code better structured and easier to maintain.
You might have noticed in an earlier example I was using a `get_var` method that wraps a call to `filter_input`. So what am I doing here? Firstly we typically use `filter_input` to access superglobal variables like `$_GET` and `$_POST`, that way we can use built in sanitization and checking filters like `FILTER_VALIDATE_INT`. Secondly, the `get_var` method wrap is just convenient so we can mock that method for testing:
// Mock the get_var method so we can spoof $_GET['site_url']
$instance->shouldReceive( 'get_var' )->with( 'site_url' )->andReturn( 'http://test.com' );
Of course making this change anywhere you access `$_GET` directly can be a fair bit of work, but having the groundwork laid for easy testing is essential. While refactoring the API plugin, I found lots of instances in the code where we want to return a response in JSON, like this:
header( 'Content-Type: application/json' );
echo json_encode( $data );
exit;
A method that contains this code is impossible to test, because as soon as PHPUnit executes that code it will `exit`, and no other tests will be run. The best way I’ve found with dealing with code like this is to move it to a separate method within the class, which in itself helps to DRY up our code:
class My_Endpoint {
public function init() {
// Do something
$data = array(...);
return $this->output_json( $data );
}
protected function output_json( $data ) {
header( 'Content-Type: application/json' );
echo json_encode( $data );
exit;
}
}
You can then mock the `output_json` method in your test case so it doesn’t actually run as defined:
$instance = \Mockery::mock( '\My_Endpoint')->makePartial();
$instance>shouldReceive( 'output_json' )->andReturnUsing( true );
$this->assertTrue( $this->instance->init() );
Dependency Injection
I talked before about hard dependencies in code and how that presents an issue for testing. One solution is to use the Dependency Injection technique and refactor the code to have the class instances passed to the classes/methods that need them, instead of declaring them inside. Dependency Injection is a whole topic in itself and something I’ll be exploring further in an email series about advanced PHP development for WordPress developers.
Wrapping Up
I hope this guide has proved useful and perhaps made unit testing a little more approachable. I cannot stress enough how valuable unit testing is, and how it has helped me as a developer. Refactoring to make code testable has helped me write code better the first time. Having a good test coverage has been a safety net many times against introducing bugs during development.
Are you a unit testing pro, is there anything I’ve missed? What other aspects of unit testing would you like to see covered? Let me know in the comments below.
3 Comments