“Load More” Button on Multiple Loops

“Load More” Button on Multiple Loops

More and more blogs today are eschewing pagination in favor of loading additional posts on the same page via ajax. WordPress has no shortage of plugins that add such functionality, but most tend to be too general or too specific. Jetpack’s Infinite Scroll is one of the most popular and easy implementations, being the recommended method for VIP-hosted sites, but even it has a serious limitation in that it can only operate on the main loop. When nothing else fits, sometimes the best course of action is to create your own solution.

For this tutorial, we’re going to create our own implementation of a “Load More” button, one capable of handling multiple loops on one page. My approach has a few steps:

  • A template tag will render the button, specific to each loop
  • Clicking the button will fire some javascript that will request the loop’s archive page via ajax
  • The loaded page will be parsed and the posts will be pulled out and appended to my existing posts, along with the new Load More button for loading the next subsequent page

I’ve created a site using the Twenty Fifteen theme that comes with WordPress and filled it with some dummy posts to get the ball rolling. To help my screenshots display a little bit more content than the theme allows out of the box, I’ve also restyled the main loop slightly to display the posts as tiles and set the site to display four posts at a time.

"Load More" blog

Here’s what the relevant area of my index.php looks like at the moment:

<div id="primary" class="content-area">
   <main id="main" class="site-main" role="main">

   <?php if ( have_posts() ) : ?>

      <h2>Posts</h2>

      <?php
      while ( have_posts() ) : the_post();
         get_template_part( 'content', get_post_format() );
      endwhile;

      ?>
      <div class="clear"></div>
      <?php

      // The theme's default pagination
      the_posts_pagination( array(
         'prev_text'          => __( 'Previous page', 'twentyfifteen' ),
         'next_text'          => __( 'Next page', 'twentyfifteen' ),
         'before_page_number' => '<span class="meta-nav screen-reader-text">' . __( 'Page', 'twentyfifteen' ) . ' </span>',
      ) );

   else :
      get_template_part( 'content', 'none' );
   endif;
   ?>

   </main><!-- .site-main -->
</div><!-- .content-area -->

This would normally be the perfect situation in which to implement Jetpack’s Infinite Scroll. With just a tiny bit of setup, the navigation at the bottom can be replaced with a Load More button that will load in subsequent posts underneath the existing ones without reloading the entire page. However, my homepage is not going to stay so simple for long because I want to display a second post loop on the same page.

I’ve gone ahead and created my second set of posts, assigning them to a category to differentiate them from my regular posts.

"Load More" post list

To display the second set of posts, I’m going to have to create a second Loop and a new WP_Query to feed it. It’s good practice to separate out logic and presentation so this should go outside of my template file. I’m going to add a template tag that returns my new query in the theme’s functions.php file.

The following code will return a new WP_Query object with just the posts in my new category, while excluding those same posts from my main query.

/* Get custom posts query */
function voce_get_custom_posts() {
   return new WP_Query( array(
      'cat' => 2,
   ) );
}

/* Don't display custom posts in main query on the homepage */
add_action( 'pre_get_posts', function( $query ) {
   if ( $query->is_home() && $query->is_main_query() ) {
      $query->set( 'category__not_in', '2' );
   }
} );

Now I can loop over my new query and output just my custom posts, while those same posts will be excluding from the main loop.

<div id="primary" class="content-area">
   <main id="main" class="site-main" role="main">

   <?php
   // Get custom posts query via my new template tag
   $custom_post = voce_get_custom_posts();
   if ( $custom_post-> have_posts() ) :

      ?>
      <h2>Custom Posts</h2>
      <?php

      // Loop over custom posts
      while ( $custom_post->have_posts() ) : $custom_post->the_post();
         get_template_part( 'content', get_post_format() );
      endwhile;

      ?>
      <div class="clear"></div>
      <?php

   else :
      get_template_part( 'content', 'none' );
   endif;

   // Reset post data since I'm done with my custom query
   wp_reset_postdata();
   ?>

   <?php if ( have_posts() ) : ?>

...

My new loop is now rendering above the main loop, with each loop displaying the correct posts.

"Load More" custom posts

Now that I have my posts, it’s time to add my Load More buttons. I’m going to create a template tag to render the button. As before, the logic portion of the code should not go into the template itself but rather into functions.php.

/* Load More */
function voce_load_more( $loop_query = null ) {
   // Get the main query if one isn't specified
   if ( ! $loop_query ) {
      global $wp_query;

      $loop_query = $wp_query;
   }

   // Get the total number of pages
   $max_page = ( empty( $loop_query->max_num_pages ) ? 1 : $loop_query->max_num_pages );

   // Get the current page, and use that to get the next page
   $paged = ( empty( $loop_query->query_vars['paged'] ) ? 1 : $loop_query->query_vars['paged'] );
   $next_page = intval( $paged ) + 1;

   // If there are no more pages, we don't need the "Load More" button anymore
   if ( $next_page > $max_page ) {
      return '';
   }

   // Allow filtering on the URL, since we'll need to point each query to its respective archive URL
   $url = apply_filters( 'voce_load_more_url', next_posts( $max_page, false ), $loop_query, $next_page );

   // Render the Load More button
   ?>
   <div class="load-more">
      <span class="load-more-indicator">Loading...</span>
      <a class="load-more-link" href="<?php echo esc_url( $url ); ?>">Load More</a>
   </div>
   <?php
}

I also need to build out the URL that each loop should use to access subsequent posts. This is the URL to the archive for just that loop’s query and needs to be set for each loop.

For my custom query, I can just check for the cat query_var, as that is all that defines that query. The archive permalink structure for my custom query would be the /category/TERM archive page.

/* Set archive URL for custom loop */
add_filter( 'voce_load_more_url', function( $url, $query, $next_page ) {
   if ( ! empty( $query->query_vars['cat'] ) && 2 == $query->query_vars['cat'] ) {
      $url = sprintf( '%s/category/custom-loop/page/%d', site_url(), $next_page );
   }

   return $url;
}, 10, 3 );

For my main query, things are a little bit more complicated. Even though this is the main query, I still need to specify a custom URL to use for its Load More buttons because I need the archive page of just that query; with the way the site is currently set up, the main query will just use index.php for its archives (which now contains two loops!). As such, I have to go with the /category/uncategorized archive page, which in this case singles out just the posts that would show up in my main query. This means I need two checks to determine which query I’m working with: check for the category__not_in query_var for the homepage, and check for the “Uncategorized” category archive for subsequent pages.

/* Set archive URL for main loop */
add_filter( 'voce_load_more_url', function( $url, $query, $next_page ) {
   if ( ! empty( $query->query_vars['category__not_in'] ) ||
      ( ! empty( $query->query_vars['cat'] ) && 1 == $query->query_vars['cat'] ) ) {
      $url = sprintf( '%s/category/uncategorized/page/%d', site_url(), $next_page );
   }

   return $url;
}, 10, 3 );

With my new template tag created, I can now use it to build out the Load More buttons in both my templates: index.php (which loads the first page of both query) and archive.php (which loads the subsequent pages of each query).

<div id="primary" class="content-area">
   <main id="main" class="site-main" role="main">

   <?php
   $custom_post = voce_get_custom_posts();
   if ( $custom_post-> have_posts() ) :

      ?>
      <h2>Custom Posts</h2>
      <div class="load-more-wrapper">
         <div class="load-more-container">

            <?php
            // Loop over custom posts
            while ( $custom_post->have_posts() ) : $custom_post->the_post();
               get_template_part( 'content', get_post_format() );
            endwhile;
            ?>

         </div>
         <div class="clear"></div>
         <?php
         // Load More for custom query
         voce_load_more( $custom_post );
         ?>
      </div>
      <?php

   else :
      get_template_part( 'content', 'none' );
   endif;

   // Reset post data since I'm done with my custom query
   wp_reset_postdata();
   ?>

   <?php if ( have_posts() ) : ?>

      <h2>Posts</h2>
      <div class="load-more-wrapper">
         <div class="load-more-container">

            <?php
            // Loop over main query
            while ( have_posts() ) : the_post();
               get_template_part( 'content', get_post_format() );
            endwhile;
            ?>

         </div>
         <div class="clear"></div>
         <?php
         // Load More for main query
         voce_load_more();
         ?>
      </div>
      <?php

   else :
      get_template_part( 'content', 'none' );
   endif;
   ?>

   </main><!-- .site-main -->
</div><!-- .content-area -->

The buttons are rendering on the homepage now.

"Load More" link

Next, I have to update the archive.php template.

<section id="primary" class="content-area">
   <main id="main" class="site-main" role="main">

   <?php if ( have_posts() ) : ?>

      <header class="page-header">
         <?php
            the_archive_title( '<h1 class="page-title">', '</h1>' );
            the_archive_description( '<div class="taxonomy-description">', '</div>' );
         ?>
      </header><!-- .page-header -->

      <?php // The part that matters! ?>
      <?php // -------------------------------- ?>
      <div class="load-more-wrapper">
         <div class="load-more-container">
            <?php // Start the Loop ?>
            <?php while ( have_posts() ) : the_post(); ?>

               <?php get_template_part( 'content', get_post_format() ); ?>

            <?php endwhile; ?>
         </div>
         <div class="clear"></div>
         <?php voce_load_more(); ?>
      </div>
      <?php // --------------------------------

 // If no content, include the "No posts found" template.
 else :
 get_template_part( 'content', 'none' );

 endif;
 ?>

 </main><!-- .site-main -->
</section><!-- .content-area -->

In all instances, the voce_load_more() tag is called inside the .load-more-wrapper div, right after the .load-more-container div which houses the actual posts. This allows the javascript to treat each .load-more-wrapper div individually as separate loops. Time to add the javascript that will do just that.

(function($){

   // Attach "Load More" functionality
   $('body').on('click', '.load-more a', function(e) {

      // Don't actually follow the href in the <a>
      e.preventDefault();

      var $el = $(e.target),      // the <a>
         $resp = $('<div>'),      // a <div> to store our new posts
         $parent = $el.parent();  // the <div> wrapper for the current loop

      // Hide the "Load More" button and show the "Loading..." indicator
      $parent.addClass('loading');

      // Get posts (and new Load More button) from archive page and append them to our existing posts 
      $resp.load($el.attr('href') + ' .load-more-wrapper', function() {

         $parent.removeClass('loading');

         var $container = $el.parents('.load-more-wrapper').find('.load-more-container'),
            $new_posts = $resp.find('.load-more-container').children(),
            $load_more = $resp.find('.load-more') || '';

         $container.append($new_posts);

         $el.parent().replaceWith($load_more);
      });
   });

})(jQuery);

That’s it! I now have two loops on my homepage, each with a corresponding Load More button. Further, the links on the buttons lead to the actual archive page for that loop. I’ve used this code on several sites now, and (after accounting for styling) the only real modifications necessary are to the filters that determine the URL for the buttons. Hopefully this helps someone out as much as it’s helped me.

Better Embeds with Media Explorer

Better Embeds with Media Explorer

I had the pleasure of speaking last month at the UtahWP meetup (formerly WP-SLC) in Draper, UT.

The Platforms Team recently used Media Explorer as the foundation for embedding content from a variety of sources for a client project.  In the course of discussing the plugin, we discovered that many people – experienced WordPress developers and users alike – were completely unaware of its existence.

For the uninitiated, Media Explorer was originally built by Code For The People and is used on WordPress.com for Twitter and Youtube embeds.  It extends the “Add Media” workflow and provides developers the opportunity to add their own embeds without knowledge of Backbone.js.

In addition to introducing Media Explorer and what it does, my talk covered:

  • How to add service integrations to Media Explorer (my example using the Meetup API)
  • Using Media Explorer to insert shortcodes instead of embed URLs
  • Reusing Media Explorer components to create a “featured Youtube video” workflow

You can view the presentation here (fullscreen recommended):

Be sure to check out the example code on Github as well, it’s the only place to see the “featured Youtube video” functionality: https://github.com/jeffstieler/WP-SLC-MEXP-Demo

Unit Testing for WordPress – Part 3: Annotations, Coverage Reports and a Real World Example

Unit Testing for WordPress – Part 3: Annotations, Coverage Reports and a Real World Example

The last few things that I want to cover in this series are annotations, coverage reports and show a real life example of how unit tests can help in the real world.

Annotations

Annotations are notes made in a PHPDoc Block before specific test methods that give directions to PHPUnit while running tests. As of this writing, PHPUnit’s webpage lists roughly 28 (plus some variants) assertions. We’ve covered @dataProvider in the last post, I want to cover several more.

Annotation: @covers

Where does it go: PHPDoc Block above the testing method

What does it do: Tells PHPUnit which method the test is covering. When running coverage reports it limits the test’s scope to that method and will not apply coverage to any other method. It’s a good way to keep your coverage report honest. See the section below for more information on coverage reports.

Usage: note the syntax is Class_name::method_name

/**
  * @covers Demo_Plugin::init
  */
function test_init() {

    //codecodecodecodecode...

}

Annotation: @codeCoverageIgnore, @codeCoverageIgnoreStart, @codeCoverageIgnoreEnd

Where does it go: PHPDoc Block above the tested method

What does it do: Tells PHPUnit that this method should not be considered in a coverage report. Some code is impossible or unnecessary to test, this is how you can get accurate measures of your code coverage report. If you do not want to ignore a full method, you can use @codeCoverageIgnoreStart/End

Usage:

/**
  * @codeCoverageIgnore
  */
function get_listing() {

    //codecodecodecodecode...

}

-or-


function get_listing() {

    //codecodecodecodecode...

    /**
      * @codeCoverageIgnoreStart
      */

        //codecodecodecodecode...

    /**
      * @codeCoverageIgnoreEnd
      */

    //codecodecodecodecode...

}

Annotation: @expectedException, @expectedExceptionCode, @expectedExceptionMessage

Where does it go: PHPDoc Block above the testing method

What does it do: These set up expectations for exceptions for test methods. You can be as vague or specific as you want (e.g. just use @expectedExeception vs using @expectedExeception, @expectedExeceptionCode and/or @expectedExeceptionMessage Also note that @expectedExeceptionMessage accepts Regular Expressions, very useful.

Usage: borrowed from PHPUnit.de
Tester file excerpt:

/**
  * @expectedException Exception
  * @expectedExceptionCode 20
  * @expectedExceptionMessage ‘Some Message’
  */
function test_exception() {
    $class = new MyClass
    $class->throwAnException();
}

Tested file:

class MyClass {
    public function throwAnException(){
        throw new Exception('Some Message', 20);
    }
}

Code Coverage Report

When writing test cases, you will encounter conditionals within the methods you are testing that alter the behavior of the method and its return value. That’s where code coverage reports come in handy. PHPUnit, using libraries from Xdebug, can generate these reports for you, in easy to read HTML.

Executing one is just as easy as running tests. At the command line, where you ran all of your tests already, execute phpunit –coverage-html ./coverage. This will run the tests and then generate an HTML coverage report. You can open the index.php file that is generated in the coverage directory in a browser.

Using the tests that were in the gist we notice that we do not have 91.18% coverage, a bit shy of 100% coverage.

coverage-report-resultIf you click into Above is a screenshot from the coverage report for demo-plugin.php you will see that it’s color coded. The categorize_post method has only 85% coverage and everything else is 100%.

Scrolling down you will see green, red and yellow blocks, Green means that the code was executed, Red that it was not executed and Yellow means it’s dead code. Yellow/dead code typically consists of the closing curly braces after return.

We seem to have forgotten (intentionally ;)) to test that if DOING_AUTOSAVE and DOING_AJAX are defined and that the method returns null in these instances and that the method returns null if the post is not a revision. See if you can write the test cases for these and get them to pass. Hey – homework helps us grow!

Sidebar: Learn more about what the CRAP number on coverage reports mean.

Use Case, Why Unit Test

We’re a the point where the plugin is live on our site and working well. It’s test coverage is 100% and functionally performs exactly as expected. Then John Q. Programmer gets hired. John, misguidedly and trying to make a good impression, believes he is optimizing the code by replacing, in the categorize_post method, the conditional if ( strstr( $title, $term_title ) ) {  to use strpos instead of strstr because he thinks its faster (note: it’s not). This comes at a time when the rest of the development team is away and John, not knowing what a unit test is, commits the code and pushes it out to production and pats himself on the back.

Weeks go by and editorial adds a bug ticket. Apparently, not all of the posts are being tagged properly. You wonder and being the savvy (read: OCD) programmer you check out the latest and greatest source and run the tests. Lo and behold, a failure! From the unit test, we immediately see that the post titles that start with the keyword aren’t being tagged as expected. You take a look at the git log and see John’s blunder.

His misguided idea to use strpos instead of strstr not only slows down the process, strpos returns a 0 (falsey) if the needle is in the first (zeroth) position of the post title. This causes the condition to fail and post not to be properly categorized. You revert the commit, give John a reminder on how code needs to be peer reviewed and unit tests must be ran before committing and live happily ever after.

Without a properly written unit test with good test data who knows how long it would have taken to track that bug down.

Now this example is somewhat watered down – but it demonstrates the power of well written unit tests. If you are like most programmers, your refactoring strategy is commit and pray; unit tests, while not a panacea, can greatly ease that anxiety. Like everything else in life, testing comes with time, there are few shortcuts. The bottom line, in this writer’s humble opinion, is the more you write tests, the better the tests become, the more you understand the code, the more confident of a programmer you will become. Enjoy writing tests for your code and gunning for 100% coverage.

[voce-related-posts]

Unit Testing for WordPress – Part 2: Writing Unit Tests

Unit Testing for WordPress – Part 2: Writing Unit Tests

Now that we got the administrative part out of the way let’s look at the code for which we are going to write tests. I included a plugin in the Vagrant setup covered in the last article. This plugin’s intended functionality is as follows:

  • Upon saving a post, it checks the post title for keywords retrieved from an external service.
  • If one of those keywords matches it adds the keyword to the post as a category.
  • If the category term doesn’t exist it creates it before adding it to the post.

The code for this plugin is in /wp-content/plugins/demo-plugin/demo-plugin.php; there are 6 methods:

  • init which is hooked into the WordPress init hook and fires attach_hooks() which makes the plugin work
  • attach_hooks hooks the categorize_post method into WordPress’ save_post hook and performs the action of checking for keywords
  • categorize_post does the majority of our work, it grabs the external service (via the get_listing method and compares the keywords with the saved post’s title, executing the process_terms method when it finds a match.
  • process_term checks if the category term exists, and if necessary fires the create_term method to create it. It then attaches the category term to the post.
  • create_term adds the category term if necessary
  • get_listing makes an external call to our API of keywords.

While six methods may seem like overkill each method has a narrow scope, the single responsibility principle is meant to make our (and our teammates’) lives easier. This helps make the code easier to test, easier to digest and easier to refactor. This should become evident as we write out our tests.

Note that this plugin is solely built as a test and may seem a bit contrived – just keep in mind that the goal of this article is to explain unit testing, not show you the absolute best way to write a plugin.

Testing, Finally

Well, not *just* yet. Before we get started, I wrote tests that cover most of what we need. Feel free to review them or work along with them. Finally it’s time to get started writing tests! Go into the /tests/ directory created by WP-CLI and rename the test file to test-demo-plugin.php. Your tests should match the file names of the files they are testing and should be nested as the files you are working with are nested. This makes finding a particular test trivial. Rename the class to Demo_Test, keep it extending WP_UnitTestCase and clear out its body.

init method

Code we are testing:

function init() {
    $this->attach_hooks();
}

Create a method called test_init and let’s take a look at the method Demo_Plugin::init. This plugin only does one thing, it calls the method attach_hooks. We can test this using a test double or a mock. Mocks can be fairly complex, especially for our first test, but you can think of a mock as a stand in.

The mock takes the place of an object and can check if certain conditions are met, for example, if the object calls a particular method. For this test, we want to create a mock of Demo_Plugin and check if attach_hooks is called when we run Demo_Plugin::init() PHPUnit comes with a built in getMockBuilder that we can use as follows:

$demo_plugin = $this->getMockBuilder( 'Demo_Plugin' )
    ->setMethods( array( 'attach_hooks' ) )
    ->getMock();

What this does is creates a creates a mock object which is a simulated object that mimics the behavior of the real object of the Demo_Plugin class and allows us to check for conditions on the argument of the setMethods method, in this case ‘attach_hooks.’ We get the mocked object by chaining the getMock method to the end.

Now that we have this mock object, we need a way to say, I expect attach_hooks to be called once when the init method is executed. Luckily there’s a super simple way to do this, the syntax is as follows:

$demo_plugin->expects( $this->once() )
    ->method( 'attach_hooks' );

The object returned by the mock builder provides us with a set of methods to use including expects and method. This is saying, stand in class, I expect the method attach_hooks to be called exactly one time during this test. Let’s run the test that we have written and see what our results are.

Note to run the test, execute vagrant ssh from the directory that you ran vagrant up, navigate to /vagrant/wp-content/plugins/demo-plugin/ and then execute phpunit.

It failed, but why? We set up exactly what we expected. One thing that we forgot – and when I started I did this countless times. You have to call the method you are testing in the first place. Our final test should be:

function test_init(){
    $demo_plugin = $this->getMockBuilder( 'Demo_Plugin' )
        ->setMethods( array( 'attach_hooks' ) )
        ->getMock();
    $demo_plugin->expects( $this->once() )
        ->method( 'attach_hooks' );
    $demo_plugin->init();
}

Lo and behold. It passes.
That was pretty complex stuff – let’s quickly review mock objects. If you want to test a method based on conditions we can use PHPUnit’s mock builder. This was a very basic usage of the mock builder – there are also methods to ensure that a method is not called at all, called a specific number of times, called with specific arguments and more.

attach_hooks method

Code we are testing:

function attach_hooks() {
    add_action( 'save_post', array( $this, 'categorize_post' ) );
}

Now that we got a hard one out of the way let’s test the attach_hooks method, which should prove a little easier. Looking at the code it just attaches a single hook. WordPress provides the has_action function that is perfect for testing this: if the action exists, has_action will return the priority on the action for that function, which, by default, is 10.

All we need to do is run the attach_hooks method and check if the method was hooked into save_posts. Since this is straight forward, there’s no mock necessary. This will be our first usage of an assertion. We can assert that the call to has_action returns the expected priority. To do this, we write:

function test_attach_hooks() {
    $demo_plugin = new Demo_Plugin;
    $demo_plugin->attach_hooks();
    $this->assertEquals( 10, has_action( 'save_post', array( $demo_plugin, 'categorize_post' ) ), 'Demo_Plugin::attach_hooks is not attaching Demo_Plugin::categorize_post to save-post' );
}

This test passes as well! Assertions are a fundamental part of unit testing and there’s no shortage of assertions that come out of the box with PHPUnit. Commonly used ones are assertTrue, assertFalse, assertArrayHasKey, assertArrayNotHasKey, assertRegex, assertNotRegExp, as you can see most of the assertions also have negative assertions as well which is very convenient. You can also write your own assertions if necessary.

It’s important to also note that the third argument for assertEquals (it’d be the second for assertTrue, since you are testing just 1 item instead of 2), allows you to put a custom message in the case that the test fails.

categorize_post method

Code we are testing:

function categorize_post( $post_id ) {

    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
        return;
    }

    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    if ( false !== wp_is_post_revision( $post_id ) ) {
        return;
    }

    $this->post_id = $post_id;
    $title         = get_the_title( $post_id );
    $this->get_listing();
    if ( count ( $this->listing ) ) {
        foreach ( $this->listing as $term_title ) {

            if ( strstr( $title, $term_title ) ) {

                $this->term_title = $term_title;
                $this->process_term();

            }
        }
    }
}

Time for a challenge. We are going to write a test for the categorize_post method. This is going to introduce two new concepts, dataProviders and factories which are both conveniences provided by PHPUnit out of the box and WP_UnitTestCase, respectively.

Data providers give us a shortcut to passing multiple sets of data through a single test, as opposed to writing a different test for each dataset. Factories let us create posts and users (and attachments among other things) so that we can interact with WordPress more dynamically. We need to create a user because of the check made in the categorize_post method:

if ( ! current_user_can( 'edit_post', $post_id ) ) {
    ...
}

So our ‘current user’ has to be able to edit the post, or the method will fail before we can even test it. Our plan of attack is to create an array of keywords pairs that we will assign to Demo_Plugin::listing to test our titles against. Then we will create a posts with their titles fed through the data provider and check how many matches we have (by the number of times the process_terms method is called.

This may sound complex, but when broken down it’s not very complicated. Let’s create a test function with two arguments in it’s signature, this will be the data fed in through the data provider.

function test_categorize_post( $title, $process_term_expects ) {
    ...
}

the $title will be the title of post that we will have the factory create. the $process_term_expects will be the number of matches we expect the title to have with our sample API response. Let’s start out our test creating the post, the user that will have the right permissions, and setting the current user to that user.

$post_id = $this->factory->post->create( array( 'post_title' => $title ) );
$user_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
wp_set_current_user( $user_id );

The factory’s implementation for users and posts are very similar. You pass the create method an array of the attributes you want for the factory created post or user. Lastly we set our current user to the freshly minted administrator who will surely have the right permissions to allow us to fully test this method.

Next we mock the Demo_Plugin object we want to create. This time we want to stub out process_term and get_listing methods. The reason for process_term is so that we can count how many times our title matches the keywords we feed the object and get_listing we want to stub out because it makes an API call that we are unconcerned about as far as this test goes.

We will pass sample data to the Demo_Plugin::listing property to run the test with control data.

$demo_plugin = $this->getMockBuilder( 'Demo_Plugin' )
    ->setMethods( array( 'process_term', 'get_listing' ) )
    ->getMock();

This isn’t much different that the test we created for the test_init method. Next we need to create the expectations for these methods. Looking at the class, the get_listing method should only be called once. So just as we did with attach_hooks we do with get_listing.

$demo_plugin->expects( $this->once() )
    ->method( 'get_listing' );

It gets a little tricky with process_term, this should be dynamic, depending on what we feed into the data provider. Luckily we have prepared for this and are passing this into the test_categorize_post method as the second argument. Instead of the once method, we use the exactly method and pass in that argument.

$demo_plugin->expects( $this->exactly( $process_term_expects ) )
    ->method( 'process_term' );

This says, expect the process_term method to run the number of times the data provider tell you to run it. If this doesn’t make sense just yet it should clear up when we go over the data provider. Next lets pass our mock object the data we want to test it against. I chose two keywords and I passed them exactly how the API would pass them (after json_decoding)

$demo_plugin->listing = array( 'Matt Harvey', 'Daniel Murphy' );

Lastly, to complete the method, let’s call categorize_post with the post id which is the required argument.

$demo_plugin->categorize_post( $post_id );

Now we’re not quite done with this, we need to set up the data provider. The format for data providers is a method that returns an array of arrays. The nested arrays should have the same number of elements as arguments passed into the method, which in our case is two ($title, $process_term_expects).

We are going to pass an article title, and how many matches it has to the mocked response we created above. I typically name the method the same as the method I am passing the data with the word provider and an underscore prefixed. Here is the sample data:

function provider_test_categorize_post() {
    return array(
        array( 'Matt Harvey plays catch', 1 ),
                array( 'Everyone Happy that Matt Harvey has successful Bullpen Session', 1 ),
                array( 'Mets Lose, Daniel Murphy Shoulders the Blame', 1 ),
                array( 'Daniel Murphy and Matt Harvey Headline Mets Charity', 2 ),
                array( 'iON offers Mets fans a view never seen before', 0 ),
    );
}

Looking through these you can see that the first three only contain one of the matches. The third contains both of the keywords in my mocked set and the last contains zero. The number of expected matches is the second element of the array. Lastly, we need to tell the tested method that we want to use the provider_test_categorize_post data provider. We do that in the form of a PHPDoc block. The entire method should look as follows:

/**
  * @dataProvider provider_test_categorize_post
  */
function test_categorize_post( $title, $process_term_expects ) {
    $post_id = $this->factory->post->create( array( 'post_title' => $title ) );
    $user_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
    wp_set_current_user( $user_id );
    $demo_plugin = $this->getMockBuilder( 'Demo_Plugin' )
                ->setMethods( array( 'process_term', 'get_listing' ) )
                ->getMock();
    $demo_plugin->expects( $this->exactly( $process_term_expects ) )
                ->method( 'process_term' );
    $demo_plugin->expects( $this->once() )
                ->method( 'get_listing' );
    $demo_plugin->listing = array( 'Matt Harvey', 'Daniel Murphy' );
    $demo_plugin->categorize_post( $post_id );
}

Run the test and see that all of the tests we’ve written pass. Just to prove that they are working as expected, change the 2 from the fourth array in the data provider to 1 and see how the test fails. Each data provider pass is counted as a single test (dot).

process_term method

Code we are testing:

function process_term() {
    $this->term_object = get_term_by( 'name', $this->term_title, 'category' );

    if ( ! $this->term_object ) {
        $this->create_term();
    }

    if ( is_array( $this->term_object ) ) {

        wp_set_object_terms( $this->post_id, $this->term_object['term_id'], 'category', true );

    }

}

The final two tests are simpler and do not require mocked objects. To test process_term we instantiate Demo_Plugin, create a post using the factory, and set the properties post_id to the post id of the factory created post, term_title to “This is the title” and set term_object to false. Next we run the process_term method and then check if the term exists by using the get_term_by function and asserting that the response is not false, and that the post has the category using the has_category function and asserting that the response is true.

function test_process_term() {
    $demo_plugin              = new Demo_Plugin;
    $demo_plugin->post_id     = $this->factory->post->create();
    $demo_plugin->term_title  = 'This is the title';
    $demo_plugin->term_object = false;
    $demo_plugin->process_term();
    $term = get_term_by( 'name', 'This is the title', 'category' );
    $this->assertNotFalse( $term );
    $this->assertTrue( has_category( $term, get_post( $demo_plugin->post_id ) ) );
}

We could have mocked the method and checked if create_term method was called once, but then we wouldn’t have been able to check that the post had the category because the term would never have been created because of the stub.

create_term method

Code we are testing:

function create_term() {
    $this->term_object = wp_insert_term( $this->term_title, 'category' );
}

Finally, this is a bit overkill because we technically tested create_term in the previous test, however, we can test create_term by instantiating Demo_Plugin, setting the property term_title, calling the create_term method and using the get_term_by function to check if the term was created. As you can see, writing more concise code makes writing tests far easier. Imagine the different cases you would have to account for if you handled the entire plugins functionality in just two methods. Additionally, writing tests is not as complicated as expected, at least they weren’t for me. Next we wrap up this series by going over some more annotations to go along with dataProvider, coverage reports and we’ll you how a unit test can save you time, headaches and a swarm of bug tickets when refactoring.

[voce-related-posts]

Unit Testing for WordPress – Part 1: The Setup

Unit Testing for WordPress – Part 1: The Setup

Just hearing unit tests makes even some of the most seasoned programmers shudder. Unit testing can be such a controversial topic that some veteran programmers have heard of them but know nothing more than what they hear in the wild ranging from “testing isn’t a programmer’s job”, to “writing tests is too time consuming to be worthwhile,” to my favorite “I write code that doesn’t need to be tested.” As you will hopefully learn, these excuses are not only silly, they are the antithesis of what unit tests are about.

While it may seem like a whole blog post dedicated to setting up unit tests is complicated, you’ll see that in the end, when you have WP-CLI installed, it’s really just a few keystrokes to get going.

What is Unit Testing and Why Unit Test?

Thinking about how to define what a unit test is exactly, I took the a basic approach and checked out what Wikipedia says about them:

In computer programming, unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures are tested to determine if they are fit for use.

That’s a bit of a mouthful. I think it could be better rewritten as

Unit testing is code that runs pieces of code (i.e., functions, methods) passing specific data and ensuring the expected outcome.

Better yet…

Code that makes sure your code works as expected.

Back to those excuses about why we don’t write unit tests. While excuses are prevalent, they are unfounded (for the most part). I agree, getting in the habit of writing (more) testable code and writing tests is time consuming, but doing so can save an exponential amount of headaches. Simply put, unit testing can help you feel confident changing existing code or introducing new components and not staying up at night anxious about impending bug ticket blitzes about cascading and unexpected side-effects.

Getting Started With WordPress Unit Testing

Since the target audience for this article is for WordPress users let’s take a look at how to get started writing tests for a plugin. The only dependencies, outside of WordPress and code to be tested, to get started, are WP-CLI, Subversion and PHPUnit installed on your development environment.

For this demo I have put together a pretty simple Vagrant script with two shell scripts to get you up and running with a development environment. This requires you to have VirtualBox, Vagrant and the Vagrant DNS plugin installed.

  • To install Vagrant DNS, after installing Vagrant, run from the shell vagrant plugin install vagrant-dns.
  • After all that is installed, clone the repo (i.e. git clone https://github.com/voceconnect/unit-testing-demo).
  • CD into unit-testing-demo and run vagrant up and let vagrant do it’s magic.
  • Once done navigate to http://unittestdemo.dev/ and you should have a fully functioning WordPress installation. The wp-admin user/password is user/password, the database user/password is root/root.

The plugin we are going to write tests for will be in the directory /vagrant/wp-content/plugins/demo-plugin/ on the VirtualBox and this directory syncs with [directory you cloned the repo into]/wp-content/plugins/demo-plugin/.

Testing Scaffold

WP-CLI has a very simple to set up testing scaffold. The syntax is wp scaffold plugin-tests PLUGIN-DIRECTORY-NAME from the root directory of the site. Since WP-CLI is set up on our virtual machine, let’s ssh into it and set up our scaffold:

  • From the same directory you ran vagrant up, run vagrant ssh. This will bring you into the virtual machine.
  • CD into /vagrant
  • Set up the testing scaffold by executing wp scaffold plugin-tests demo-plugin. This should return Success: Created test files.
  • If you CD into wp-content/plugins/demo-plugin and list out the contents (ls) there should be new files (.travis.yml and phpunit.xml) and new directories (tests and bin).
  • We need to set up a testing instance of WordPress for our testing suite, which the scaffold provided us a script for, execute: bash bin/install-wp-tests.sh test-db root root localhost latest from within /vagrant/wp-content/plugins/demo-plugin. This creates a table “demo_plugin_tests” using the username/password root/root within localhost using the latest version of WordPress.

Our test suite is installed! We can make sure by running phpunit from /demo-plugin and we should see something similar to the following:

The reason it says 1 test (and because it’s green means the test passed) is that the scaffold sets up a dummy test that checks if the value of true is true, which shockingly is true (more to come on that when we go over assertions).

One last minor tweak to the files that the WP-CLI scaffolding created. Open up phpunit.xml in the plugin directory and add the following after the closing of the <testsuites> element. The phpunit.xml should look similar to below:

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

This tells PHPUnit to only look in the files in the current directory and below and ignore the tests directory – which greatly decreases the time it takes to run coverage reports.

As mentioned earlier, getting to this point may seem like it took a while, but the process is pretty straight forward thanks to the WP-CLI scripts. At this point we are ready to take the next steps on this journey towards creating a plugin and writing a set of tests that cover its functionality.

[voce-related-posts]