Advertisement

The Beginner’s Guide to Unit Testing: Building Testable Themes

by

In the first two articles in this series, we took a high-level look at what unit testing is and how to apply it in the context of plugin development. Of course, there's more to WordPress than writing plugins, isn't there? A significant part of a WordPress developers job – for some it's the most significant part – is theme development.

So in this article, we're going to take a look at how to develop testable themes. Specifically, we're going to take a look at how themes are different than plugins and then we're going to write an extremely simple theme that will be used to demonstrate the principles of unit testing and that can be applied in future development.


Understanding How Themes Are Different Than Plugins

Before we get started creating a theme or reviewing any code, it's important to understand the difference in theme and plugin development. First, plugins can be written in two ways:

  • As an object that encapsulates a set of functions (which is what we did in this article).
  • As a simple collection of functions

Both methods do the same thing: that is, they use a collection of functions and filters to introduce new functionality to WordPress. The primary difference is how the functions are encapsulated.

But when it comes to theme development, there's really only one way to develop a theme and that is by using a collection of functions defined in functions.php. This presents the following two challenges for writing unit tests for themes:

  • Because the theme isn't object-oriented, there's no way for us to actually store the object in an array like we did in the last article
  • We must determine a way to write and evaluate our theme's functions that can run independently of loading a theme into a browser

Because good themes use a collection of filters and actions, we're going to be creating a theme that follows those best practices and because the emphasis of this particular article resides in unit testing themes, more emphasis will be placed on writing the tests rather than creating a good looking, highly functional theme.


Preparing to Unit Test

Before coding, let's initialize our project's directory. We need to setup the skeleton of the theme so, in your WordPress theme directory, create a new directory for the theme. Mine is called Basic-Theme. Add the following files (we'll fill them out later):

  • footer.php
  • functions.php
  • header.php
  • index.php
  • style.css

Let's go ahead and stub out the stylesheet so that WordPress recognizes the theme and allows us to activate it from within the Dashboard. To do so, add the following code:

/*
Theme Name: Basic Theme
Theme URI: TODO
Version: 1.0
Description: A basic theme used to demonstrate how to write unit tests for themes.
Author: Tom McFarlin
Author URI: http://tommcfarlin.com
License: GNU General Public License v2.0
License URI: http://www.gnu.org/licenses/gpl-2.0.html
*/

To be complete, go ahead and add opening and closing PHP tags to the beginning and end of your functions file. This will make sure we've got the groundwork laid when we begin writing theme functions later in this article.

And add a new directory called tests. Here, we'll need to place the WordPress Tests.


WordPress Tests: A Quick Word

Earlier in this series, I provided a link to the WordPress tests located on GitHub. Although these tests fine to use, the most recent, Automattic-maintained WordPress tests are located in this Subversion repository.

If you're an advanced developer, then I recommend checking out those tests; however, if you're just getting started with unit testing – no problem! I'm providing all of the source code – including the WordPress Tests – in a GitHub repository that you'll be able to download, reference, and use for your own projects.

Once the tests are installed, your theme's directory should look like this:

Basic Theme

Because PHPUnit must be executed from the command line, you'll need to open a terminal session (or a command prompt), navigate to the tests directory, and you should be able to run them using the following command (as an example):

phpunit tests/test_user_capabilities.php

Your terminal should then output something like this:

Basic Theme
Heads up: If you're unable to execute the tests, please refer back to the first article in the series to verify your configuration. Additionally, your mileage may vary based on your operating system, web server, and local machine configuration. If you end up having to do something different, please share your notes in the comments to help others.

Basic Theme: A Unit Testable WordPress Theme

At this point, let's go ahead an activate the theme in the Dashboard. The theme should activate (if not, make sure you don't have any stray characters in your template files). If you attempt to view the theme, it will naturally display a white screen.

Before writing any tests, let's go ahead and populate our template files with a little bit of content just so we can have something show up on the frontend.

In header.php, add the following code:

<!DOCTYPE html>
<html>
	<head>	
		<meta charset="UTF-8">
		<link rel="profile" href="http://gmpg.org/xfn/11" />
		<link rel="pingback" href="<?php bloginfo( 'pingback_url' ); ?>" />
		<title><?php wp_title( '' ); ?></title>
		<?php wp_head(); ?>
	</head>
	<body <?php body_class(); ?>>
	
		<div id="header">
			This is the header.
		</div><!-- /#header -->

In index.php, add the following code:

<?php get_header(); ?>

	<div id="content">
		This is the content.
	</div><!-- /#content -->

<?php get_footer(); ?>

And in footer.php, add the following code:

		<div id="footer">
			This is the footer.
		</div><!-- /#footer -->

	</body>
</html>

Simple, I know but this will give us just enough to work with whenever we begin writing tests. Save your work, review the theme in the browser and you should see this:

Basic Theme

Writing Unit Tests

Test Theme Activation

In your tests directory, create a file called test_basic_theme.php and stub out the file to look like this:

// Include the functions for the theme
include_once('../functions.php');

class Test_Basic_Theme extends WP_UnitTestCase {
	
} // end class

Above, we're defining a class that will be used to wrap all of our theme's unit tests.

First, let's define the setUp method. The setUp method is a function provided by the WordPress Test framework and we can use it to fire certain functions before the tests run. For example, when the WordPress Tests run, they run against the default theme – that is, Twenty Eleven. In our case, we want to run the tests against our own theme.

To do this, we'll need to tell WordPress to actually switch themes before running the rest of our tests. Since this needs to happen before the tests run, it needs to be defined in the setUp method. Make sense?

So let's write our setUp method:

function setUp() {
	
	parent::setUp();
	switch_theme( 'Basic Theme', 'Basic Theme' );
	
} // end setup

Once again, let's execute our tests. We can do this by running the same command as we did when we initially setup the test:

phpunit tests/test_basic_theme.php

Permitting you've done everything correctly, you should actually see a failure when the test runs:

Basic Theme

The error message is clear, though: "No tests found in class "Test_Basic_Theme"". So let's mitigate that and write the first test for the theme. It can be something extraordinarily simple but remember from the previous post that we don't want to test just the optimal path, but the failure path, too.

As such we need to test that the Basic Theme is active and that Twenty Eleven is not active. To do this, we'll be using the assertTrue method and the assertFalse method and we'll be doing so in the context of two functions. Take a look at the code below and update your test file accordingly:

function testActiveTheme() {

	$this->assertTrue( 'Basic Theme' == get_current_theme() );
	
} // end testThemeInitialization

function testInactiveTheme() {

	$this->assertFalse( 'Twenty Eleven' == get_current_theme() );

} // end testInactiveTheme

Once again, execute the tests and you should see them run green. Nice, right?

Basic Theme

This is a relatively simple functionality, so let's consider a few advanced functions that our theme could have.

Test That jQuery Is Enqueued

Out of the box, Basic Theme doesn't include jQuery so we're going to include it with our theme. If you recall from the earlier posts, the proper unit testing methodology is as follows:

  1. Write the test
  2. Run the test (and it will fail)
  3. Write the code necessary to make the test pass
  4. Run the test (and it should pass, permitting Step 3 was done correctly)

So, let's do exactly this for jQuery.

First, we need to write a test to determine if jQuery is loaded. We'll be using the WordPress function wp_script_is. Since the theme is going through the normal page lifecycle in a browser, we'll need to manually tell WordPress to load jQuery by using the do_action function.

function testjQueryIsLoaded() {

	$this->assertFalse( wp_script_is( 'jquery' ) );

	do_action( 'wp_enqueue_scripts' );
	$this->assertTrue( wp_script_is( 'jquery' ) );

} // end testjQueryIsLoaded

Before we go any further, there's something important to note here: I dislike placing multiple assertions in a single function because I think that each function should serve to test a single purpose; however, there are exceptions. Here, we need to make sure that jQuery isn't loaded before calling do_action.

Anyway, run the test and it'll fail. So we need to add the code to functions.php that will make sure that jQuery is added to our theme. To do that, include the following function in your functions file:

function basic_add_jquery() {

	wp_enqueue_script( 'jquery' );	
	
} // end basic_remove_jquery
add_action( 'wp_enqueue_scripts', 'basic_add_jquery' );

Finally, run the test and it should be green. Easy enough, isn't it?

Test Meta Descriptions

Let's say that we want to include a default meta description on the homepage. In the simplest case, it'll be nothing more than the blog's description. So, in following with our methodology outlined above, let's introduce a function to test that the meta description string that's added to the head element matches what we expect:

function testBasicMetaDescription() {
	
	$meta_description = '<meta name="description" content="' . get_bloginfo( 'description' ) . '" />';
	$this->expectOutputString( $meta_description, basic_meta_description() );
	
} // end testBasicMetaDescription

Run it – it'll fail. Note that I'm not using the standard assertTrue, assertFalse functions – there will be more details on this in a moment. Now, let's introduce the following function into functions.php:

function basic_meta_description() {
	
	echo '<meta name="description" content="' . get_bloginfo( 'description' ) . '" />';
	
} // end basic_meta_description
add_action( 'wp_head', 'basic_meta_description' );

Notice that this function hooks into the wp_head action. In order to write the meta description out into the head element, we must echo the string rather than return the string.

Now, note that in the test above we're using expectOutputString. This is useful when we need to evaluate a function that echo's a string (rather than returns a string). Since the wp_head action is going to return a significant amount of data (that is, the entire head element), we really only need to evaluate the meta description being returned. And that's why rather than calling do_action( 'wp_head' ), I'm simply calling the function itself and evaluating the output against what I'm expecting it to have.

Once again, run PHPUnit and your tests should all pass.


Test All the Things

Clearly, we've only scratched the surface of what unit testing can do for our theme development. There's still much that can be tested – we've not even taken a look at testing The Loop, various ways to evaluate post formats, or even how to examine comments.

Remember this is a beginner's guide and the three articles cover a lot of ground.

Regardless, the principles are the same: It's a matter of making sure that you programmatically fire the appropriate function or action and evaluate its output in both expected and unexpected cases.

Finally, you can find the entire project along with documented functions in this GitHub repository.


Resources

Here's a summary of the resources used in this article:

Advertisement