Nexcess
Nexcess Blog Logo
December 08, 2020

Adding Unit Tests to an Existing WordPress Plugin

So far we’ve done little more than introduce you to the idea of building tests for your WordPress plugins and talk about a bunch of the extra terms you need to understand to dive deeper into testing your code. Today we’re going to make it practical by grabbing one of my free plugins and adding a few unit tests to show you how to put it together. 

You can find the plugin on Github or WordPress.org. Just like my previous post, I assume that you have WP CLI installed and can set up basic tests. If you can’t check out my post introducing you to unit testing in WordPress.

Unlike the last time, we only need to scaffold the tests so we can start with the following command in our WordPress installation.

wp scaffold plugin-tests wptt-ics-feeds

Now let’s get into writing a few tests.

The first thing I want to test is to make sure that the links a user sees in their profile with calendar feeds are correct. Specifically, we’re going to look at the get_subscribe_link function.

You can see the completed tests for this section here.

Let’s start by copying the default sample test file and renaming it to test-feed-links.php. I always like to create different files for the areas of the plugins I’m writing tests for, even if that means I have lots of files to deal with. It’s far easier to stay organized with clearly labelled files.

This plugin is a bit older and instantiates a global variable as it starts up. This allows us to call that global when in our setUp function so that we have access to the plugin code. We’ll also need to use the WordPress Factory to set up a new user so that we can test the links provided with that user. That means our setUp and tearDown functions should look like this.

public function setUp(){

    parent::setUp();

    // getting the plugin global

    $this->plugin = $GLOBALS['wptt_ics_feeds'];

    // make a fake user

    $this->editor = new WP_User( $this->factory->user->create( array( 'role' => 'editor' ) ) );

}

public function tearDown(){

        parent::tearDown();

        wp_delete_user( $this->editor->ID, true );

}

Now we can get to writing a test for our feed links. We’ll write two different tests to test both situations that the link function can find itself in. First, we’ll test get_subscribe_link() without any arguments.

   /**

     * Tests base feed link without author

     */

    public function test_base_feed_link(){

        $feed_link = $this->plugin->get_subscribe_link();

        $complete_link = site_url() . '/?feed=wptticsfeeds';

        $this->assertEquals( $feed_link, $complete_link, 'The feed links are not equal' );

    }

The first thing the code above does is access our plugin instance as defined in the setUp function and call the get_subscribe_link() function. Next, I hard code the expected output of the function so that I have something to compare against. Finally, we use assertEquals to compare the two values.

With that done I can head back over to terminal and run the tests with the phpunit command. If my tests pass I’ll see something like the output below. If they don’t pass then I’ll get a big red warning instead of a green bar, which means I need to figure out why they aren’t passing and fix the tests.

In this case, our tests passed and we can move on to testing the output of our link function if we pass in an author name. You can see this test below.

   /**

     * Tests feed link with author

     */

     public function test_author_feed_link(){

        $feed_link = $this->plugin->get_subscribe_link( array( 'author' => $this->editor->ID ) );

        $complete_link = esc_url( site_url() . '/?feed=wptticsfeeds&wpttauthor='. $this->editor->user_login );

        $this->assertEquals( $feed_link, $complete_link, 'The feed links with author are not equal' );

     }

Here we do almost the same thing as we did when we tested our link previously. The change is that we pass in the user we created with our setUp function and then test to make sure that this link comes out as expected with assertEquals.

Now, let’s move on to testing the custom filter inside the plugin.

Testing a WordPress Filter with PHPUnit

I’ve had some disputes with other developers about testing filters in the past. Some don’t bother testing their internal plugin filters, but I think that you should be testing these filters. Sometimes filter names change and you forget about this so don’t document it anywhere or check for usage of the filter. Writing a simple test for your filter will highlight this because when you change the filter name a test error will happen.

For this test, we’ll add a new file to our tests folder called test-filters.php. I’ll use this file to test all future filters that need to be tested in the plugin. This time our setUp function only needs to instantiate an instance of our plugin and our tearDown function doesn’t need to do anything. See the code below.

   public function setUp(){

        parent::setUp();

        // getting the plugin global

        $this->plugin = $GLOBALS['wptt_ics_feeds'];

    }

    public function tearDown(){

        parent::tearDown();

    }

Next, we need to write the test for our filter which you can see below.

   /**

     * Tests that the post where time can be changed with a filter

     */

    public function test_posts_where_filter(){

        add_filter( 'wptt_ics_feeds_how_old', array( $this, 'new_where' ), 10, 2 );

         $output = $this->plugin->two_months( '' );

         $date = date('Y-m-d', strtotime( $this->new_where() ) );

         $this->assertStringContainsString( $date, $output, 'The date filter did not work' );

    }

    public function new_where(){

        return '-1 week';

    }

The first thing we do is call our filter and then pass it our new_where function. I always like to write a separate function for filter tests because I have ended up using them in multiple tests enough that I feel this saves work later. Our new_where function will pass the string -1 week to our filter.

Next we call our two_months() function inside the plugin. Then we use a standard PHP date function to get the format we expect for the date. Since I’m mostly concerned that the date is parsed properly in the plugin I use assertStringContainsString to check to see if the output of the two_months function contains the same date string as the $date variable.

Again, if your tests pass, then it should all be green. If they fail you’ll get a big red warning instead of the pleasant green bar.

Why Don’t We Test the ICS Feed Output

Note, that I didn’t test the final output of our ICS feed. While this is possible, it’s got a bunch of moving parts that could fail and have nothing to do with my code. I could send the ICS feed to an online validator and then receive the JSON response and parse it to check if it’s valid.

If the HTTP request fails, my test fails. If the online validation service shuts down, my test fails. There are a bunch of other scenarios that could also cause my test to fail for no reason that’s my fault. Because of this, I chose not to test the final feed programmatically and figured that I could test it by subscribing to a feed in my calendar and seeing that my posts were in fact on the calendar as expected.

This Isn’t Unit Testing

I’m sure that some of you are looking at this and saying that I’m not writing unit tests, and you’d be correct. I’m writing integration tests because my code is integrating with WordPress for the tests to work. Yes, you can use WP_Mock to fake WordPress to write true unit tests, but most of the time I care that my code works with WordPress.

Today, we looked at adding a few tests to an existing WordPress plugin as a practical example of how testing can work for your projects. To continue learning, check out the business case for adding testing to your process as a business owner. It can be hard to see past the upfront expense since development will take longer, but it does pay off.

Avatar for Curtis McHale
Curtis McHale
Curtis is a husband, father, developer and business coach. He specializes in helping people build a business that lets them spend time with their family instead of working all the time.