Author Archives: J.D. Grimes

WP Ajax Plugin Unit Testing

The other day I was refactoring some code for an Ajax callback in one of my plugins. I was dreading having to manually test that the changes I was making didn’t break anything, when I had an idea. “Why don’t I have unit tests for this?”, I though to myself. Before I started trying to mock Ajax requests myself, I thought I’d ask Google what was already out there. As it turns out, the WordPress unit test framework already provides a testcase specifically for testing Ajax callbacks. Who knew? Obviously, the developers did, but I hadn’t thought much about it before, except that each time you run the WP unit tests, you get a message that it the Ajax unit tests aren’t being run.

While I found this testcase in the WordPress PHPUnit test source code, what I didn’t find was any tutorials on how it works. So, I just used WP’s AJAX unit tests as an example. Its pretty simple actually.

The WP_Ajax_UnitTestCase

If you’re familiar with plugin unit testing, you know that your testcases need to extend the WP_UnitTestCase class to leverage the full power of the WordPress testing framework. Well, for Ajax tests, its the same thing, only you need to extend a child of WP_UnitTestCase: WP_Ajax_UnitTestCase. This testcase provides a few methods that make the Ajax unit tests simpler.

The most important method is of course the one that actually mocks the Ajax request: _handleAjax(). It takes just one parameter; the name of the Ajax action:

$this->_handleAjax( 'my_ajax_action' );

Setting up for the test

Of course, before you run the Ajax handler, you will need to set up for the test. The first thing you need to do is set up the $_POST data. You need to mock up the $_POSTed data, and then later we’ll check that we get the expected result.

$_POST['_nonce'] = wp_create_nonce( 'my_nonce' );
$_POST['other_data'] = 'something';

Setting the expected exception

The test is performed by running the 'wp_ajax_*' action, and thus invoking the callback. (No request is actually made.) When the Ajax callback function runs, it should finish by calling die() or exit(). Well, actually, it will need to be using wp_die(), because if you call die() you will kill the tests. Lesson: in WP Ajax callbacks, you should use wp_die(). Fortunately, I already was in the callbacks that I was testing, so I was good to go.

Now, when wp_die() gets called, the testcase catches it and prevents it from actually killing the execution of the script. Instead, it throws an exception. The testcase uses two different exceptions, and which one gets thrown depends on whether there was any output from callback (this is caught with a buffer). If there was output, a WPAjaxDieStopException is thrown. Otherwise, a WPAjaxDieContinueException is thrown. These need to be caught. One way that this is done is using the setExpectedException() method provided by PHPUnit.

If, for example, you are expecting the callback to exit without any output, you would do this:

$this->setExpectedException( 'WPAjaxDieStopException' );
$this->_handleAjax( 'my_ajax_action' );

If you need to run some assertions afterward, you’ll need to catch the exception manually instead. For example, your Ajax callback may be supposed to save some data to the database, and you’ll want to check that the data was actually saved.

try {
	$this->_handleAjax( 'my_ajax_action' );
} catch ( WPAjaxDieStopException $e ) {
	// We expected this, do nothing.
}

$this->assertEquals( 'yes', get_option( 'some_option' ) );

Checking for the Correct Output

If your Ajax function outputs some data, you will probably want to check that the expected data gets output. If you are sending a simple response code with wp_die(), you’ll probably want to check that the expected value was output. The message passed to wp_die() is the message of the exception that gets thrown. So one way to check for this is when you set the expected exception:

$this->setExpectedException( 'WPAjaxDieStopException', '1' );

In this case we are expecting '1' to get output to signify a successful result.

If you need to manually catch your exception so you can run some other assertions like we did above, you’ll need to do it differently:

try {
	$this->_handleAjax( 'my_ajax_action' );
} catch ( WPAjaxDieStopException $e ) {
	// We expected this, do nothing.
}

// Check that the exception was thrown.
$this->assertTrue( isset( $e ) );

// The output should be a 1 for success.
$this->assertEquals( '1', $e->getMessage() );

$this->assertEquals( 'yes', get_option( 'some_option' ) );

But if you are outputting your data directly instead of using wp_die(), you need to do it differently. One example might be if you are using wp_send_json_success() to send a JSON response. In this case, the output is caught by _handleAjax() and stored in $this->_last_response. So you could check for the correct JSON response like this:

try {
	$this->_handleAjax( 'my_ajax_action' );
} catch ( WPAjaxDieStopException $e ) {
	// We expected this, do nothing.
}

$response = json_decode( $this->_last_response );
$this->assertInternalType( 'object', $response );
$this->assertObjectHasAttribute( 'success', $response );
$this->assertTrue( $response->success );

Setting the User Role

Many times an Ajax callback isn’t intended for all users. You may be saving something that only users with administrator permissions should be allowed to save. The Ajax test case class provides a handy helper function to let you set the current user’s role. For example, if your Ajax callback should only work for administrators, you may want to test that just any subscriber can’t come along and use it. To do this, you would set the user’s role before calling _handleAjax(), like this:

$this->_setRole( 'subscriber' );

And if you want to make sure that the administrators will have the permissions necessary to use it, you would set the role to 'administrator' instead:

$this->_setRole( 'administrator' );

You can also run as a logged-out user:

$this->logout();

Running Your Tests

Based on the examples above, our final testcase might look something like this:

/**
 * Test case for the Ajax callback to update 'some_option'.
 *
 * @group ajax
 */
class My_Some_Option_Ajax_Test extends WP_Ajax_UnitTestCase {

	/**
	 * Test that the callback saves the value for administrators.
	 */
	public function test_some_option_is_saved() {

		$this->_setRole( 'administrator' );

		$_POST['_wpnonce'] = wp_create_nonce( 'my_nonce' );
		$_POST['option_value'] = 'yes';

		try {
			$this->_handleAjax( 'my_ajax_action' );
		} catch ( WPAjaxDieStopException $e ) {
			// We expected this, do nothing.
		}

		// Check that the exception was thrown.
		$this->assertTrue( isset( $e ) );

		// The output should be a 1 for success.
		$this->assertEquals( '1', $e->getMessage() );

		$this->assertEquals( 'yes', get_option( 'some_option' ) );
	}

	/**
	 * Test that it doesn't work for subscribers.
	 */
	public function test_requires_admin_permissions() {

		$this->_setRole( 'subscriber' );

		$_POST['_wpnonce'] = wp_create_nonce( 'my_nonce' );
		$_POST['option_value'] = 'yes';

		try {
			$this->_handleAjax( 'my_ajax_action' );
		} catch ( WPAjaxDieStopException $e ) {
			// We expected this, do nothing.
		}

		// Check that the exception was thrown.
		$this->assertTrue( isset( $e ) );

		// The output should be a -1 for failure.
		$this->assertEquals( '-1', $e->getMessage() );

		// The option should not have been saved.
		$this->assertFalse( get_option( 'some_option' ) );
	}
}

And our Ajax callback might look like this:

/**
 * Ajax callback to save the 'some_option' option.
 */
function my_some_option_ajax_callback() {

	check_ajax_referer( 'my_nonce' );

	if ( ! isset( $_POST['option_value'] ) || ! current_user_can( 'manage_options' ) ) {
		wp_die( '-1' );
	}

	update_option( 'some_option', $_POST['option_value'] );

	wp_die( '1' );
}
add_action( 'wp_ajax_my_ajax_action', 'my_some_option_ajax_callback' );

Notice that I added @group ajax to the testcase docblock? That isn’t necessary, but you will probably want to do it, because it lets you easily run just your Ajax tests. Also, you will probably want to exclude the Ajax group from running by default, the same way WordPress does. The reason is because the tests can be very slow. The Ajax tests are so slow because all of the default Ajax callbacks in WordPress core get hooked up before each test is run. There are quite a few, and this takes some time. I don’t think this feature is actually necessary, and maybe it will be removed in future, especially if the test suite starts preserving hook state between tests. It also calls 'admin_init' every time, which is also slowing things down.

So that’s it. There really isn’t much to the Ajax unit tests, but it saved me from breaking the code I was refactoring. And hey, after all, that’s what unit tests are for.

Dear WP plugin developer, please…

Follow Coding Standards

WordPress has its own coding standards, which I highly recommend. But if you don’t like them, use some different ones. I really probably won’t care. But, please, please, please, stick to some kind of standard that is readable. If you don’t follow any standard, your code is inconsistent at best or hardly readable at worst.

I’m not asking you to do this for the sake of myself and other developers, who might want to fork your plugin (if it is open source). I’m asking you to do this for your own sake. If you can’t read your code, it is more likely to be broken, and more difficult to fix. If you want to maintain your plugin, you should write maintainable code. For your own sanity.

Only enqueue scripts where needed

Just once in your life, try maintaining a site that uses about 50 or 100 plugins that are enqueueing scripts. Guess what. Your site will be handling requests for all sorts of scripts and styles, sending KB or even (yes, really) MB of data that it doesn’t even need to. Oh, and did I mention that it could also make your site kind of slow (unless you are using a good CDN, then it might load reasonably fast)? The moral of the story? Register your scripts on 'init' or 'wp_enqueue_scripts', by all means. But never, ever, ever enqueue a script or style unless and until you absolutely have to.

Prefix function names

Not doing this is one way to cause conflicts, and even kill sites!

Real world examples of bad function names:

  • user_list()
  • register_settings()
  • transition_db()

So, every function (and class, and global variable, and option, and post meta field, etc.) should have a prefix unique to your plugin. Many plugins use a kind of acronym, like bp_ for “BuddyPress”. This makes the function names shorter and easier to write, and that’s is all right to a point. But consider that a prefix like that may not really be as unique as you think. Taking the letters A to Z and the numbers 0 to 9, there are:

  • 36 1-character prefixes
  • 1,296 2-character prefixes (but WordPress already uses wp_, so make that 1295)
  • 46,656 3-character prefixes

That’s a total of 47,987 unique prefixes in three or less characters. Now consider that there are currently over 31,000 plugins on WordPress.org alone. Within a few years that 50,000 mark will probably be a reality. I think it is a safe bet that the two-character acronym for your plugin is probably already being used by another plugin. And the 3-character prefixes will soon run out as well. So you may want to carefully consider using your plugin’s entire name as a prefix (if it is reasonably short), or a longer prefix, say 4 or 5 characters at least. For example, in my WordPoints plugin, every function is prefixed with wordpoints_. (I couldn’t have used wp_ anyway since WordPress already uses it.)

Another idea is to use a shorter prefix on some functions, and only the longer prefix on others. Although this introduces inconsistency, it might still be a good choice in some circumstances. For example, if you had a plugin called Post Colors, you could use a prefix like pc_ on functions that were unique to the purpose of the plugin, like pc_add_color_to_post(). It’s unlikely that there will be another plugin with the pc_ prefix that has a function with that name. But then consider a function to update your plugin’s settings. You could call it pc_update(), but that is much more likely to be used in another plugin. In that case you could call the function post_colors_update() instead.

Personally, I’d prefer to choose a long, unique prefix and use it consistently throughout the plugin.

Use sprintf() with L10n instead of splitting sentences

It’s in the plugin developer handbook now, so I’ll just quote from there:

Use format strings instead of string concatenation – translate phrases and not words –

printf(
__( 'Your city is %1$s, and your zip code is %2$s.', 'my-theme' ),
$city,
$zipcode
);

is always better than

__( 'Your city is ', 'my-theme' ) . $city . __( ', and your zip code is ', 'my-theme' ) . $zipcode;

Don’t use error silencing

I’ve been surprised how many plugins I find that heavily use error silencing. While there are rare cases in which it really is useful to use the @ symbol to silence errors, when used often it can easily introduce unexpected side effects. Once I spent hours (probably more than 6) chasing a bug. I knew that a fatal error was occurring, because only half of the post would get output, and then after than nothing. The funny thing was, no matter what I did, I couldn’t get PHP to give me an error. Not a single peep. I finally discovered that the cause was a shortcode. The shortcode function was complex, and called a bunch of other functions that called a bunch of other functions, etc. I was finally able trace the point where the script was dying to a bit of code that was calling a function, with (you guessed it), an @ in front of it (to silence any pesky notices). (Removing the @ revealed the problem was that the devs hadn’t heeded my advice about checking for PHP libraries before using them.) I haven’t been able to use the @ symbol lightly since. Hey, it only takes an instant to type, but it can take hours to un-type. You already know the moral of the story: don’t use @ to silence errors. Ever.

(This disclaimer is included for those of you who are thinking to yourself, “Hey, there are times that it can actually be useful.” Yeah… if I remember correctly, I think I came upon one of those situations once…)

But actually that isn’t all. Some plugins do error_reporting( 0 ). It’s true. They really do. And guess what! They don’t even set the error reporting back to the previous level afterward. There are two reasons that plugins shouldn’t be doing this. You already know what one of them is. The other is that this is an INI setting. Generally speaking, plugins shouldn’t be changing the INI settings unless they have a good reason. A very good reason. The site administrator has the INI settings the way they are for a reason (OK, I know, many users haven’t a clue what the PHP INI settings are, so their host has the INI settings the way they are for a reason). Changing them is a bad idea and makes debugging harder.

Check that a PHP library is available before using it

I’ve met with these fiends on several occasions (one instance is recounted above). It is easy to assume that it is safe to use EXIF functions, for example. But the truth is that not every instance of PHP is compiled with EXIF. Sometimes the library isn’t being used. You have to think of this before you start using it, or else you will break websites. Guaranteed. Like I said, I’ve encountered this more than once. Suddenly, something mysteriously doesn’t work.

The solution is to make sure you check that the function exists before you use it. Sometimes it may not be necessary to use the function at all. In other cases it may be integral to your plugin, and in that situation you should be showing the user a notice when the function(s) aren’t available.

Conclusion

OK, so I apologize for the rant. If you’re a new plugin dev, and you just realized that you’ve made some of these mistakes, don’t worry. You’re not alone. I have made some of them myself, because we all start somewhere. That is really part of the beauty of WordPress. Almost anyone can start out just wanting to build a website, and end up as a plugin developer. And we all end up looking back at those first plugins we wrote, and thinking, “Did I really do that?”. Yep. We did.

So, I didn’t write this post just to bash newbie plugin developers and all of their poor practices. (Only part of me did. :) Rather, I wrote it to possibly help a few plugin developers improve their skills. And I’ll probably be adding new sections to it in the future (each time I need to rant).

Unit Testing WordPress Plugin Uninstallation

If you’re a WordPress plugin developer, there are two things that you should probably be doing. First, you should be writing your plugin with an uninstall routine to clean everything up when a user decides to remove it. Second, you should be unit testing your plugin, to make sure that everything is working. Actually, we could go a step further: You should be unit testing your plugin’s uninstallation script. It kind of follows naturally doesn’t it?

I thought so, so I created a set of tools to make that easier: the WP Plugin Uninstall Tester

From the project README:

The purpose of this testcase is to allow you to make plugin uninstall testing as realistic as possible. WordPress uninstalls plugins when they aren’t active, and these tools allow you simulate that. The installation is performed remotely, so the plugin is not loaded when the tests are being run.

I created these tools after finding that there was a fatal error in one of my plugin’s uninstall scripts. Not that I didn’t have unit tests for uninstallation. I did. But the uninstall tests were being run with the plugin already loaded. So I never realized that I was calling one of the plugin’s functions that wouldn’t normally be available. That’s when I decided to create these testing tools, so my uninstall tests would fail if I wasn’t including all required dependencies in my plugin’s uninstall script.

In addition to providing a realistic uninstall testing environment, it also provides some assertions to help you make sure that your plugin entirely cleaned up the database.

That basically sums up the project. I encourage you to add it to your plugin’s test suite. And it’s hosted on GitHub, so you’re welcome to help make it even better.

WordPress Unit Test Suite Introduces @expectedIncorrectUsage

In an recent effort to clean up the WordPress unit tests, the @expectedDeprecated notation was introduced for testing deprecated functions. Now another new notation was introduced which does a similar thing for _doing_it_wrong(). Usually, you don’t want to be _doing_it_wrong(), but you might want to test that the proper warning is given from your code when someone does it wrong. And that is where @expectedIncorrectUseage comes into play. Now your tests will fail if you are _doing_it_wrong(), which is helpful to make sure you are coding your project correctly. But when you are testing for a _doing_it_wrong(), you are expecting it, so you can add the @expectedIncorrectUsage to your tests. Then your test will fail if you don’t receive the expected _doing_it_wrong() warning.

The usage is simple:

<?php

/**
 * A test class with failing tests.
 */
class Failing_WP_UnitTestCase extends WP_UnitTestCase {

	/**
	 * This test will succeed, because we were expecting to do it wrong.
	 *
	 * @expectedIncorrectUseage register_uninstall_hook
	 */
	public function test_with_expected_incorrect_usage() {
		
		// Only static functions can be registered as an uninstall callback.
		register_uninstall_hook( __FILE__, array( $this, 'a_function' ) );
	}
	
	/**
	 * This test will fail, because we weren't expecting to do it wrong.
	 */
	public function test_with_expected_incorrect_usage() {
		
		// Only static functions can be registered as an uninstall callback.
		register_uninstall_hook( __FILE__, array( $this, 'a_function' ) );
	}
	
	/**
	 * This test will fail, because we don't do this wrong, though it was expected.
	 *
 	 * @expectedIncorrectUseage register_uninstall_hook
	 */
	public function test_with_expected_incorrect_usage() {
		
		// Only static functions can be registered as an uninstall callback.
		register_uninstall_hook( __FILE__, array( __CLASS__, 'a_function' ) );
	}
}

This will be useful for those that are heavily using the WordPress unit test framework, like running plugin unit tests.

Don’t do_shortcode()

If you do much WordPress development, sooner or later you’ll find that you want to call a shortcode programmatically. It’s easy to do this:

do_shortcode( '[shortcode]' );

But that is a very bad idea. The do_shortcode() function is expensive, and is actually a roundabout way of accomplishing this. We know exactly what shortcode we want to call, and what we want the attributes and content to be. But do_shortcode() doesn’t know that. It will use an intensive REGEX to evaluate that simple little string, searching for the shortcode. Instead, since we already know what the shortcode is, we should just call the function that handles that shortcode. Easy enough right?

For a function like this, it is simple:

function my_shortcode() {
	return 'my shortcode';
}
add_shortcode( 'shortcode', 'my_shortcode' );

We can just call the function directly: my_shortcode().

This is simple enough, but it does have a drawback. If this function is part of a project outside your control, there is the possibility that the function might get renamed. Also, there are cases where this approach is not even possible, and I encountered one of these this week.

The shortcode function was a class instance method, and there was no way to access the instance. Kind of like this:

class my_shortcode_class {

	public function __construct() {
		add_shortcode( 'shortcode', array( $this, 'my_shortcode' ) );
	}

	public function my_shortcode() {
		return 'my shortcode';
	}
}

new my_shortcode_class();

Now how are you going to call that programmatically, without do_shortcode()? I couldn’t just create a new instance of the class, because there was other stuff in the constructor that should only run once.

Well, here’s how I did it. All of the shortcode functions are stored in the global $shortcode_tags array. So, I just pulled the function from that based on the shortcode tag, and called it using call_user_func(). Here is a simple wrapper for this, which supports passing attributes and content:

/**
 * Call a shortcode function by tag name.
 *
 * @author J.D. Grimes
 * @link https://codesymphony.co/dont-do_shortcode/
 *
 * @param string $tag     The shortcode whose function to call.
 * @param array  $atts    The attributes to pass to the shortcode function. Optional.
 * @param array  $content The shortcode's content. Default is null (none).
 *
 * @return string|bool False on failure, the result of the shortcode on success.
 */
function do_shortcode_func( $tag, array $atts = array(), $content = null ) {

	global $shortcode_tags;

	if ( ! isset( $shortcode_tags[ $tag ] ) )
		return false;

	return call_user_func( $shortcode_tags[ $tag ], $atts, $content, $tag );
}

Examples:

$result = do_shortcode_func( 'shorcode' );

$result = do_shortcode_func( 'shortcode', array( 'attr' => 'value' ), 'shortcode content' );

So next time you want to do_shortcode(), you can use this wrapper function, or implement something similar within your code that needs to call a shortcode like this.