Tutorial: Writing a Unit Test for WordPress

Standard

WordPress Unit Testing is an expanding field in the WP world. Currently, there is a big push to add more tests, and you can get in on the action!

If you need help getting started with Unit Testing, check out my previous post about why to get into WordPress Unit Testing. It includes a short section about how to get rolling.

The Basics

The way the test works is relatively simple: a developer makes changes to a WordPress file, then runs the tests to make sure nothing breaks because of their changes. It notifies the dev if he/she needs to change something in their code.

Think of tests as a way to prove that a function still works properly. It tests for the expected output, so sometimes writing a test isn’t very satisfying because, in theory, your test will cause no reaction because everything is working fine.

Important

Unit tests are written to ensure WordPress returns the expected output. To be clear, they are not written to specifically test one’s code, meaning you don’t usually write a test for the sole purpose of testing your new code. To put it another way, Unit Tests exist to prove WordPress works, and if you run your code against all of the WP Unit Tests, you can see if your edits affect the output that WP creates, you can see if it breaks WP’s basic functionality.

A Basic Unit Test

Unit Tests are written then submitted to WordPress Trac to be included in the repository of Unit Tests. They can be very basic or very complicated, but they always seem to do obvious things; they make sure input is processed into the expected output.

I think this can best be explained through example. The following Unit Text will almost work. For the sake of clarity, I’ve left out some of the class information, but check it out as an example to get an idea of how a test is written.

/**
 * Test to see if get_post works.
 *
 * Compares a post ID ($org_post_id) with post ID 
 * taken out of get_post ($new_post_id).
 * If they don't match, get_post() doesn't work, and it will 
 * return an error.
 */
function test_get_post() {
    //Create new post using method provided by WP
    $org_post_id = $this->factory->post->create();
 
    //get post object using the new post's ID
    $post_obj = get_post( $org_post_id );

    //Get the post ID as given to us by get_post
    $new_post_id = $post_obj->ID;

    //Use pre-defined method to test if the two ID's match
    $this->assertEquals( $org_post_id, $new_post_id );
        
}

The purpose of this test is to see if the mechanism of get_post() is broken by whatever changes you submit with your new code. This test should work fine, as long as get_post() works. If it fails, then you know you’ve done somthing to mess up get_post().

A Full-Featured Example

This is a Unit Test I’m working on for the upcoming save_post{$post_type} action hook. The hook is all ready, and this test that I have written just needs to be tested. So, it may have some errors, but it will give you the general idea of how a test works. Check out the comments in the Gist for information on how it works. To fully understand it you’ll need to have a good grasp on Object Oriented Programming (OOP).

If the font size bothers you as much as it does me, you can view the gist here.

<?php
/**
* This has been tested using the diff for save_post_{$post_type}
* https://core.trac.wordpress.org/changeset/25050
*
* @todo Try different post type names
* @todo Create two posts and try to update them both at the same time
*/
class Save_Post_Post_Type {
/**
* Loads our control data into the test post.
*
* First, we'll save the post using wp_update_post and without save_post_{$post_type}
* We'll use this as a control to compare our $result against later.
* We can't just compare the raw post input with the save_post_{$post_type} because saving
* the post strips slashes, validates, etc.
*
* @param int $test_post_id ID of the post we are testing with.
* @return string $control_result content of post saved with wp_update_post
*/
function control_test( $test_post_id ) {
$control_post = array();
$control_post['ID'] = $test_post_id;
$control_post['post_type'] = 'book';
$control_post['post_content'] = "Initial Data.";
$control_post_id = wp_update_post( $control_post );
/**
* when we save it, it returns a post ID,
* so we'll need to get our content back out of the control post.
*/
$control_post = get_post( $control_post_id);
$control_result = $control_post->post_content;
return $control_result;
}
/**
* Test to see if save_post_{$post_type} hooks into the saving action correctly.
*
* @param int $test_post_id ID of the post we are testing with.
* @param string $input Input from provider() method.
* @return string $test_result Content of post(s) updated with save_post_{$post_type}
*/
function test_save_post_post_type( $test_post_id, $input ) {
//Run the hook that should exist, and attempt to change the data.
add_action( 'save_post_book', $this->modify_content( $test_post_id, $input ) );
//Get post content by id
$result_post = get_post( $test_post_id );
$test_result = $result_post->post_content;
return $test_result;
}
/**
* Save our post with new post_content, on 'save_post_book'
*
* @param int $test_post_id ID of the post we are testing with.
* @param string $input Input from provider() method.
* @return void
*/
function modify_content( $test_post_id, $input ) {
/**
* Attempt to overwrite 'the auto-generated post_content' with $input
* This will be done on 'save_post_book' in test_save_post_type()
*/
$test_post_new_content = array();
$test_post_new_content ['ID'] = $test_post_id;
$test_post_new_content['post_content'] = $input;
wp_update_post( $test_post_new_content );
}
}
/**
* Set Up Test
*/
class Tests_Save_Post_Post_Type extends WP_UnitTestCase {
/**
* Establish testing variables
*
* This information will be passed as the parameter ($input) into our test method in this class.
* Note that we use @dataProvider to let test_save_post_post_type know we're using this method
* Note also that it returns a multidimentional array (an array within an array).
* These values will be passed one at a time into our testing function
*
* @return array Array of input content to test "save_post_{$post_type}"
*/
public function provider() {
return array(
array( '023983775900' ),
array( '(*&@#)*)&*#@*~_)*?(#' ),
array( 'http://wordpress.org&#39; ),
array( 'It\'s a trap!' ),
array( 'These aren\'t the droids you are looking for.' ),
array( 'I find you lack of faith disturbing.' ),
array( 'Ready are you? What know you of ready?' ),
);
}
/**
* Tests to ensure save_post_{$post_type} hooks into save_post correctly.
*
* If these $input and $result don't match, the test will throw an error.
* They should match, displaying that the input matches the output ($result),
* when saved by "save_post_{post_type}".
*
* This function must begin with "test".
*
* @param string $input Input from provider()
* @return bool Pass/Fail test Results
*
* The following line is required to let the test know what method is sending the $input
* @dataProvider provider
*/
public function test_run_test( $input ) {
$test = new Save_Post_Post_Type;
//Create Post using extended class WP_UnitTestCase
$test_post_id = $this->factory->post->create();
$control_result = $test->control_test( $test_post_id );
/**
* Run the test. Send the input, return the result.
*
* This method changes the content of our post into a Star Wars quote.
* If it does not change, it will thow an error. Remember, our original content was,
* "Initial Data."
*/
$test_result = $test->test_save_post_post_type( $test_post_id, $input );
/**
* Compare $control_result content with $test_result content.
*
* Since we attempted to modify the content of the "Book" post, this method "assertFlase"
* checks to see that these two statements do not match.
* There are many other methods you can use here:
* http://phpunit.de/manual/current/en/appendixes.assertions.html
*/
$this->assertNotEquals( $control_result, $test_result);
}
}

A Note

Weston Ruter added a comment greatly simplifying this process, which would be the best way to run this test. One of its greatest benfits is that it is extremely compact, although a bit advanced for a less-experienced developer, which may be you. I’ve decided to leave “the long way” to run this test here in this post so you can view more of the features and techniques of Unit Testing.

More

I hope you were able to learn something from viewing the actual code from a Unit Test. I’ve found that always helps me much more than reading long explanations. However, if you have any questions, I’m glad to answer them. Unit Tests are a real need in WordPress, and I’d love to help the community by assisting future Test authors.

One thought on “Tutorial: Writing a Unit Test for WordPress

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s