Unit Testing Ajax and API Requests in WordPress Plugins

I am scared of unit testing. There, I’ve said it. Unit testing is one of those practices that I know I should be doing, there are benefits to doing it, but it just seems too hard to implement, especially with an established codebase.

Recently I have been building our next plugin and throughout its development I have had a nagging voice in my head constantly reminding me about unit tests. So I’ve taken the plunge and started writing tests. In this post I will introduce you to unit testing a WordPress plugin, using my recent experience as a guide.

What is Unit Testing

Let’s get this out the way early: unit testing is the practice of testing the smallest units of code, usually functions and methods, to make sure they are working correctly. Typically this means passing in data to a method for a specific scenario and ensuring the result or return data is as expected.

Unit testing is actually quite simple, which was one of my main misconceptions that originally put me off doing it. Take WP Offload S3 as an example. The plugin uploads files to Amazon S3. Right, so how far should I test?

When testing the upload method, should the test actually check if the file exists on S3 after the upload has been performed? No it shouldn’t. The test shouldn’t even use the Amazon Web Services API at all, but we will come to that later.

Testing Setup

WP-CLI is the best way to setup a complete WordPress unit testing environment locally. A prerequisite is PHPUnit, the de facto framework for unit testing PHP applications. WP-CLI will install the WordPress testing library and a set of configuration files for loading the plugin and library during testing.

One of the most important features of the testing library is the WP_UnitTestCase class which extends from PHPUnit’s PHPUnit_Framework_TestCase. Every test case you write should extend it, as it provides some very helpful functionality around preparing the WordPress environment before and cleaning up after every test.

WP_UnitTestCase also exposes an instance of the WP_UnitTest_Factory object, which is extremely useful for quickly creating typical WordPress objects. For example, if you are testing a method that adds some metadata to a post, you don’t need to worry about creating that post from scratch. Your test would look something like this:

function test_add_meta() {
    $post = $this->factory->post->create_and_get();
    MyClass::add_meta( $post->ID );
    $expected_meta_value = 'Foo';
    $this->assertEquals( $expected_meta_value, get_post_meta( $post->ID, 'test_meta_key' ) );
}

Remember, WP-CLI’s setup of the testing environment is just a scaffold that, although it works perfectly well out of the box, can be customized and tweaked to suit the needs of your plugin and tests. Check out Ian’s article about how we set up our unit testing environment for WP Migrate DB Pro.

Areas of Testing

There are some aspects of WordPress code in plugins that at first glance seem untestable. How do you test Ajax callbacks and API requests? What about interaction with WordPress’ own core functions? Here are some of the problem areas that were holding me back in the past, and how to test them.

Mocking

Earlier I mentioned testing WP Offload S3 which communicates with the Amazon Web Services API. When testing a class that uses the API, I simply need to test that class and its methods. The tests have no interest in the API, AWS or even HTTP. Moreover, how would you run your tests if Amazon was experiencing issues with their API?

This is where Test Doubles and Mocking come into the picture. Here is a sample test case with a partially mocked class that inherits from the API class my tests will utilize. The setUp() method of the test case is great to store shared code between tests:

function setUp() {
    $this->api = $this->getMockBuilder( '\DeliciousBrains\Offload\AWS\API' )
                          ->setConstructorArgs( array( API_KEY, API_SECRET ) )
                          ->setMethods( array( 'request' ) )
                          ->getMock();
                          
    $this->wp_error_response = new \WP_Error( 100, 'Issue with AWS' );
}

Using setMethods in the mock builder means that only those methods are mocked, the rest of the methods will work as normal. If you omit that directive, all methods will return null.

I’ve also added a property to hold a WP_Error that will be used a bit later. The API class has a method called get_files(), which asks AWS to return files. On success the method should return an array of files, and when something goes wrong it will throw a WP_Error. Here is the test for that method:

$successful_response       = new \stdClass;
$successful_response->data = array( array( 'id' => 1, 'name' => 'image.jpg' ) );

$this->api->expects( $this->any() )
                  ->method( 'request' )
                  ->will( $this->onConsecutiveCalls( $successful_response, $this->wp_error_response, array() ) );

// Test for successful response
$this->assertEquals( $successful_response, $this->api->get_files() );

// Test for WP_Error (Error) response
$this->assertTrue( is_wp_error( $this->api->get_files() ) );

// Test for empty array response
$array_response = $this->api->get_files();
$this->assertTrue( is_array( $array_response ) );
$this->assertCount( 0, $array_response );

Basically we are spoofing the return from the request() method and making sure our method handles all three scenarios correctly. Using onConsecutiveCalls() you can supply different return values for multiple calls of the method. Pretty cool right? We are only testing the unit of our code, without dependencies on outside factors, systems, or services.

The full sample test case is here:

Ajax

Unit testing Ajax, huh? Won’t that involve Javascript? Just like HTTP requests, our unit tests don’t care about something like Javascript triggering requests, so instead we can mock up those requests. The WordPress test library makes testing Ajax callbacks a breeze, and removes the need to do any manual mocking of admin-ajax.php.

Our test cases need to extend the WP_Ajax_UnitTestCase class and the important method is _handleAjax(), which mocks the triggering of an Ajax action hook like:

add_action( 'wp_ajax_as3cfpro_initiate_upload', array( $this, 'ajax_initiate_upload' ) );

So inside the test you would call $this->_handleAjax( ‘as3cfpro_initiate_upload’ ); to trigger the callback, but there are a couple of other things to consider as well. Depending on what the callback returns or calls when it finishes, informs how you construct your test case. In this example, the callback returns some JSON data via wp_send_json_success, therefore we need to catch that data and verify it before exiting:

Callback:

function ajax_initiate_upload() {
    check_ajax_referer( ‘initiate-upload’ );
    …
    wp_send_json_success( ‘Upload initiated’ );
}

Testcase:

function test_ajax_initiate_upload() {
    // Spoof the nonce in the POST superglobal
    $_POST['_wpnonce'] = wp_create_nonce( ‘initiate-upload’ );

    try {
        $this->_handleAjax( ‘as3cfpro_initiate_upload’ );
    } catch ( WPAjaxDieStopException $e ) {}
 
    $response = json_decode( $this->_last_response );
    $this->assertInternalType( 'object', $response );
    $this->assertObjectHasAttribute( 'success', $response );
    $this->assertTrue( $response->success );
    $this->assertObjectHasAttribute( data, $response );
    $this->assertEquals( ‘Upload initiated’, $response->data );
}

This article goes into further detail about the various callback returns and how to handle them in your Ajax tests.

Making your Code Better

Unit testing is great for catching issues with code as they happen, but I think an important benefit I have seen by adding unit tests to the codebase is how it has helped shape and improve the code itself.

When testing certain classes with overly long and complex methods, tests have been written alongside refactoring of these methods, resulting in more maintainable and reusable code.

If you are working on projects with other developers then having these tests automated with a tool like Travis CI will help to keep on top of any issues that can arise with an ever changing codebase.

TDD

The alternative to putting off unit testing writing a bunch of unit tests retrospectively, is practicing the dark art of TDD: Test Driven Development. TDD is all about a cycle of writing failing tests and then writing enough code to pass the test. Each iteration will add more missing functionality into the test, that is then added and refactored into the existing code.

Carl Alexander has written a great introduction to unit testing in WordPress with a TDD focus, that I recommend you read. To be honest, the more I read about TDD, the more I want to start implementing it, especially for new features. Plus, testing as you go, either via TDD or just being dedicated to writing tests, has to be better than writing tests for a complete codebase in one big hit, as well as being proactive rather than reactive in finding issues with your code.

I hope this has been a helpful insight into unit testing for WordPress plugins, and made it a little easier to get started writing tests for your code.

Are you a unit testing maestro? Share your WordPress related testing tips in the comments below.

About the Author

Iain Poulson

Iain is a WordPress and PHP developer from England. He builds free and premium plugins, as well as occasionally blogging about WordPress. Moonlights as a PhpStorm evangelist.