Tag Archives: Plugin Development

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.

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!