Writing WordPress Plugin Unit Tests

Writing unit tests for your WordPress plugins might sound like a daunting task, but trust me, once you take the plunge and learn how, it won’t be so hard after all. Actually, it will make your life a whole lot easier.

I am just starting my own journey into unit testing as I write this. I’d never heard of the WordPress unit tests, or PHPUnit. But I stumbled through the process of setting things up as a newbie to the command line. I hope this tutorial will make your initiation into plugin unit testing smoother than mine.

Install it first! #

Before we go further, you’ll need to download the WordPress developer tools, and install PHPUnit. I originally did this through WP-CLI (which you’ll need to install if you want to use it).

To install the WordPress developer suite manually, you can run the following from the command line:

# Make the directory for the tools (assumes that ~/svn exists; 
# you can create it by running "$ mkdir ~/svn")
$ mkdir ~/svn/wordpress-dev

# Change to the new directory we just made.
$ cd ~/svn/wordpress-dev

# Check out the developer tools with SVN.
$ svn co http://develop.svn.wordpress.org/trunk/

Next you will need to follow the instructions in the /trunk/tests/phpunit/README.txt file to set up the tests configuration. Once that is done, if you want to run the tests for WordPress itself, just run the phpunit command from the /trunk directory:

# Change to the trunk directory.
$ cd ~/svn/wordpress-dev/trunk/

# Make sure the checkout is up to date.
$ svn up

# Run all of the tests.
$ phpunit

# Run only, e.g., the cache tests.
$ phpunit tests/phpunit/tests/cache

Setting Up Your Plugin for Testing #

Now that you’ve installed all of the necessary components, you will need to set up your plugin so it can be tested in the same way. If you’ve installed WP-CLI, you can do that automatically from the command line. Whether you do that or not, it’s always a good idea to understand how this works.

The first thing that you will need to do is decide where you want your plugin’s tests to reside. You could put them in a /tests/ subdirectory within your plugin, or you could set it up like the WordPress developer tools, with your plugin in a /src directory, and your tests in a /tests directory. But you don’t have to limit yourself to either of these. You can have your tests anywhere you want, and you can always change things around later (but if you do you’ll have to make a few changes to the configuration). For now, we’ll assume that your plugin’s checkout is in ~/svn/myplugin, and your tests are in ~/tests/myplugin. Then you’ll run the tests like this:

# Change to the directory with our plugin's tests.
$ cd ~/tests/myplugin

# Run the tests.
$ phpunit

To make this work, we’ll need to create a phpunit.xml file in the directory. When you run the phpunit command from that directory, it will look for that file, and read the configuration from it. You can start with the following configuration:

<phpunit
	bootstrap="bootstrap.php"
	backupGlobals="false"
	colors="true"
	convertErrorsToExceptions="true"
	convertNoticesToExceptions="true"
	convertWarningsToExceptions="true"
	>
	<testsuites>
		<testsuite>
			<directory prefix="test-" suffix=".php">./</directory>
		</testsuite>
	</testsuites>
</phpunit>

This configuration will load the /bootstrap.php file that we will create in a moment. It will also load all the .php files in the directory that begin with test-. Those will be your testcases.

Now we’ll need to create our bootstrap.php file. This file needs to manually load your plugin when the tests are run. The plugin needs to be loaded manually because it won’t be active on the WordPress install (no plugins will — remember this is a test environment). We can do this by hooking into the muplugins_loaded filter. The only problem is that we can’t use the add_filter() function like we normally would, because we need to hook up the function before the WordPress test environment is loaded (we’ll do that in a moment by includeing the bootstrap file from the WordPress tests). Once we load the WordPress test environment, it will initiate the tests, and then it will be too late to hook into any actions or filters after that. This is why the testing tools provided with WordPress include the function tests_add_filter(), which we can access after loading /trunk/tests/phpunit/includes/functions.php. We can use it exactly like we would use add_filter(), but before WordPress has been loaded.

So our bootstrap file will look something like this:

<?php

/**
 * Set up environment for my plugin's tests suite.
 */

/**
 * The path to the WordPress tests checkout.
 */
define( 'WP_TESTS_DIR', '/Users/me/svn/wordpress-dev/trunk/tests/phpunit/' );

/**
 * The path to the main file of the plugin to test.
 */
define( 'TEST_PLUGIN_FILE', '/Users/me/svn/myplguin/myplugin.php' );

/**
 * The WordPress tests functions.
 *
 * We are loading this so that we can add our tests filter
 * to load the plugin, using tests_add_filter().
 */
require_once WP_TESTS_DIR . 'includes/functions.php';

/**
 * Manually load the plugin main file.
 *
 * The plugin won't be activated within the test WP environment,
 * that's why we need to load it manually.
 *
 * You will also need to perform any installation necessary after
 * loading your plugin, since it won't be installed.
 */
function _manually_load_plugin() {

	require TEST_PLUGIN_FILE;

	// Make sure plugin is installed here ...
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );

/**
 * Sets up the WordPress test environment.
 *
 * We've got our action set up, so we can load this now,
 * and viola, the tests begin.
 */
require WP_TESTS_DIR . 'includes/bootstrap.php';

Just paste that into your bootstrap.php file and make sure WP_TEST_DIR and TEST_PLUGIN_FILE are defined properly, and your plugin is ready for testing!

(Note: If you use WP-CLI to generate the scaffolding for you plugin tests, the setup will differ slightly from that described here.)

Writing Your Tests #

You are now ready to begin writing your tests. Remember to prefix the files with the tests in them with test- if you are using the XML PHPUnit configuration shown above.

A basic test will look something like this:

<?php

/**
 * An example test case.
 */
class MyPlugin_Test_Example extends WP_UnitTestCase {

	/**
	 * An example test.
	 *
	 * We just want to make sure that false is still false.
	 */
	function test_false_is_false() {

		$this->assertFalse( false );
	}
}

Now for some tips!

Use WP_UnitTestCase

When you are writing your tests, don’t extend the PHPUnit_Framework_TestCase class as shown in the PHPUnit docs. Instead, use WordPress’s own extension of this class included in the testing suite, called WP_UnitTestCase. It is defined in includes/testcase.php, and offers extra functionality to help you write your tests more easily.

Object Factories #

One of the advantages of using WP_UnitTestCase is its factories. These can be accessed through the factory member variable. The factory is an object with properties that are each an instance of one of the classes defined in includes/factory.php. What do they do, you ask? They make it very simple to create users, posts, terms, etc., wherever you need them in your test. So, instead of doing this:

$args = array(
	/* A bunch of user data you had to make up */
);

wp_insert_user( $args );

You can just do this:

$user_id = $this->factory->user->create();

But wait, it gets even better. What if you need many users (or posts, or whatever)? You can just create them in bulk like this:

$user_ids = $this->factory->user->create_many( 25 );

That will create 25 users that you can use in your test.

The factory has the following properties that you can use:

  • $post
  • $attachment
  • $comment
  • $user
  • $term
  • $category
  • $tag
  • $blog

They may all be used in the same manner as demonstrated in the above example with the $user factory. For example, you can create a post like this:

$this->factory->post->create();

You can also specify particular arguments to use for creating the object. In the above example we created a post, but it wasn’t assigned to a particular user (the post_author field will default to 0). Sometimes we may want the post assigned to a user instead. We’d do that like this:

$user_id = $this->factory->user->create();

$post_id = $this->factory->post->create( array( 'post_author' => $user_id ) );

Also, if you need more than just the ID of the object you are creating, you don’t need to do this:

$post_id = $this->factory->post->create();

$post = get_post( $post_id );

Instead, use the create_and_get() method:

// $post will be an instance of WP_Post
$post = $this->factory->post->create_and_get();

In this example, we used the post factory, but the same is true for all of the factories.

Set Up and Tear Down #

One of the great things about using the WP_UnitTestCase class as a parent for your test cases, is that it will clean up everything before your test is run, so that it won’t be thrown off by leftovers from other tests. It does this using the setUp() and tearDown() methods which are automatically called by PHPUnit before and after each test method is run, respectively. This is great, and will save you hours of debugging if you remember one important thing: Whenever you need to implement these methods in your child class, be sure to call parent::setUp() and parent::tearDown() at the top of each function, respectively. If you don’t, the sky may fall on your head.

This automated cleanup means that you generally don’t need to worry about cleaning things up, either before or after your tests. Just test away, and WP_UnitTestCase will clean up after you. For example, if you add an option in the test, you don’t need to delete it afterward. In the tearDown(), WP_UnitTestCase performs a ROLLBACK query, and in setUp(), the caches are flushed.

Conclusion #

If you haven’t written any unit tests for your plugin yet, now is the time to start! It makes it very easy to test whether your plugin is functioning properly as you develop it, and isn’t too difficult to set up and maintain. If you run into trouble or find any tips along the way, be sure to leave a comment below. Happy testing!

20 thoughts on “Writing WordPress Plugin Unit Tests

  1. Pingback: WordPress Unit Testing: Deprecated Functions | Code Symphony

  2. Tom

    Great post. Your example has finally enabled me to set up my plugin testing environment. What I was missing was the second bootstrap.php file.

    Reply
  3. Pingback: Programmatically‎ Creating a WordPress Widget Instance | Code Symphony

  4. Pingback: Unit Testing WordPress Plugin Uninstallation | Code Symphony

  5. Zane Matthew

    Kudos, this is one of the more comprehensive examples/tutorials on writing WordPress plugin unit test. I notice one small issue when copy/pasting your code. You forgot the “Class” in your test example, this: MyPlugin_Test_Example extends WP_UnitTestCase { should be Class MyPlugin_Test_Example extends WP_UnitTestCase {

    Reply
  6. Tuan Anh

    I found your post via an answer on StackExchange while looking for an unit test in WordPress. This is a very helpful resource for developers! Thank you so much for sharing!

    Reply
  7. Hans-Helge

    Thx for this great post. I already wanted to implement unit Tests for my plugin but I couldn’t figure out how to do it with WP. This post helped me a lot and now I can start testing :D

    Reply
  8. Pingback: WP Ajax Plugin Unit Testing | Code Symphony

  9. Rafa

    Hey,
    Thanks for the nice post. Unfortutunately, When I try to run the tests, it gives me an error: ‘Fatal error: Class WP_UnitTestCase not found in on line 6’. Do you have any idea of what is causing this problem? I’m about to get crazy, trying to find a solution.

    Reply
    1. J.D. Grimes Post author

      Are you sure that you have set up your bootstrap.php file correctly? Check if it is actually being executed by adding var_dump( __FILE__ ); in it. If it isn’t, maybe your phpunit.xml file has a problem.

      Reply
  10. Coby

    Hey JD,

    Great post. Definitely one of the best articles I’ve seen on the topic of automated testing in WP.

    Just wanted to let you know, Google search results link to the HTTPS version of this page, which doesn’t get styled correctly because of Mixed Content JS errors like this one:

    Mixed Content: The page at ‘https://codesymphony.co/writing-wordpress-plugin-unit-tests/’ was loaded over HTTPS, but requested an insecure stylesheet ‘https://codesymphony.co/wp-content/plugins/syntaxhighlighter/syntaxhighlighter3/styles/shThemeDefault.css?ver=3.0.9b’. This request has been blocked; the content must be served over HTTPS.

    Reply
  11. Josh Habdas

    Hello! I’m writing some unit tests for my new WordPress plugin. I’ve created the codebase in a procedural manner and use namespaces for encapsulation. Currently I have one PHP file which implements the entire plugin using just handful of methods and would like to increase my code coverage levels with unit testing.

    I’m aware of the class-based methods for testing mentioned in this post, and I’m aware WP Core uses class-based tests. But as someone who’s worked with atomic state and JavaScript for some time I recognize the downsides to designing using the `class` keyword.

    So as I begin to write my unit tests using PHPUnit, I want to ensure I set some good patterns from the outset and leverage procedural testing (a la Mocha) as much as possible for my procedural code. Based on the info I dug up in my searches it seems OOP took the PHP world by storm and, as a result, there are few resources out there which seem to describe best practices for creating unit tests in a procedural manner.

    Any pointers on where to start for writing procedural tests with PHP? If I need to leverage classes to tap into WordPress goodies later I will. But not until there’s a clear need which exposes itself through iteration (the Extreme Programming approach).

    For reference, here’s the plugin: https://github.com/comfusion/hyperdrive

    Thanks to anyone for your help. But please, please do not try and get me to “see the light” regarding OOP because that’s not what this question is about. Thank you kindly for your inputs.

    Reply
    1. J.D. Grimes

      This is an interesting question. PHPUnit is obviously built using objects, and expects testcases to be classes. However, you could probably create a custom testcase class that just had a job of running your tests written in procedural code. You would of course need to still leverage some of the class-based code from PHPUnit in those tests, like for the assertion methods, but I notice that that is also true in Mocha—the assertion methods are defined on an object.

      Basic mock-up of how this could work:

      class Test extends WP_UnitTestCase {
      
      	/**
      	 * @dataProvider data_provider
      	 */
      	public function test( $test ) {
      		
      		$func = include( $test );
      		
      		$func( $this );
      	}
      	
      	public function data_provider() {
      		return glob( 'tests/*' );
      	}
      }
      

      Then each test file would contain one procedural function that would be returned at the end of the file:

      <?php
      
      return function ( $assert ) {
      	$assert->assertFalse( true );
      };
      

      The functions could also be named of course, and just the name of the function returned.

      I’d be interested to hear how something like this would work out.

      Reply
      1. Josh Habdas

        Use of a data provider looks very encouraging and is not something I’d considered previously for enabling testing of procedural code. At first blush it looks as though it would remove a considerable amount of repetition (boilerplate) in the test portion of the codebase help achieve “Deceptive Simplicity” (see HBR article dated 2011) for the units under test.

        During my search yesterday I discovered a testing library called Kahlan which uses the “describe-it” technique to enable testing of code without use of the `class` keyword, so I’m going to give that a shot and, when necessary, loop back here to see about using the data provider approach once more complex or WP-integrated testing becomes necessary. Good stuff, J.D. Thank you kindly for your input and thoughtfulness.

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *