12.4. Unit Test Scripts, Actions, Filters
In the previous blog, we saw how to write tests for plugin methods that returns value or echo output. In this blog, we explore third category of plugin methods where outcome is tested using corresponding WordPress API functions.
For example, add_action() which registers an action hook is tested using WordPress function has_action().
But, before exploring such tests, we briefly explain an utility class used by Share on Social Plugin test cases to reduce boilerplate code in tests and also, learn to toggle user capability to test methods which depends on user capability.
Utility Methods for Tests
To investigate the arrays, fetch object from database and change user
role or capability etc., tests require multiple lines of code. The class
Util
defined by share-on-social/tests/phpunit/util/class-util.php
,
extracts such redundant boilerplate code into utility methods.
Methods to investigate the multidimensional arrays and check whether action exists.
has_value() - whether multidimensional array contains a value.
has_obj() - whether multidimensional array contains an object of a class.
has_action() - wrapper for WordPress has_action() function.
has_filter() - wrapper for WordPress has_filter() function.
get_action() - returns action array from global array.
Methods to fetch posts and change locale.
get_post_id() - get post id for matching meta key and value.
count_posts() - get number of posts of a post type.
change_locale() - changes locale for the tests.
Toggle Capability
Util
class also defines methods to manage users and capabilities
within tests.
set_cap() - enable or disable a capability.
set_activate_plugins_cap() - enable or disable activate_plugins capability.
set_admin_role() - enable or disable admin role to an user.
In many of the tests, setUp() method uses following constructs to set the user and capability to run the tests.
share-on-social/tests/phpunit/tests/test-options.php
class Test_Sos_Options extends WP_UnitTestCase {
public function setup () {
parent::setup();
....
wp_set_current_user( 1 );
Util::set_admin_role( true );
}
Having gone through the utility class and its methods, let’s get back to the tests that uses WordPress API functions to assert the outcome of method under test.
Test Actions and Filters
In Share on Social Plugin classes we use setup() method to add actions and filters.
For example, Sos_Options::setup()
method adds actions for
admin_init and admin_menu hooks.
share-on-social/admin/class-options.php
class Sos_Options {
public function setup () {
add_action( 'admin_init',
array( $this,'init_common_options' ) );
add_action( 'admin_menu',
array( $this,'register_settings_page' ) );
}
We test actions in Test_Sos_Options
using utility method
Util::has_action()
.
share-on-social/tests/phpunit/tests/test-options.php
class Test_Sos_Options extends WP_UnitTestCase {
public function test_setup () {
$this->assertFalse(
Util::has_action( 'admin_init', $this->sos_options,'init_common_options' ) );
$this->assertFalse(
Util::has_action( 'admin_menu', $this->sos_options,'register_settings_page' ) );
$this->sos_options->setup();
$this->assertTrue(
Util::has_action( 'admin_init', $this->sos_options, 'init_common_options' ) );
$this->assertTrue(
Util::has_action( 'admin_menu', $this->sos_options, 'register_settings_page' ) );
}
In Test_Sos_Options::test_setup()
, we first test that admin_init and
admin_menu hooks’ actions are not set. Next, we call method under test
with $this->sos_options->setup(). Finally, we test whether
admin_init has action pointing to Sos_Option::init_common_options()
method. For this, we use Util::has_action()
which is wrapper for
WordPress function
has_action().
Similarly we test admin_menu action.
Filters are tested, in same fashion, using Util::has_filter()
method
which is wrapper for WordPress function
has_filter().
Demarcate Unit Tests
Is it necessary to test the outcome of action or filter. No, remember that we unit test the plugin and not the WordPress core. It is sufficient to test whether action is registered for the hook and thereafter, it is responsibility of WordPress core to handle the action.
Test Enqueue Scripts
Next type of tests are related to enqueue of plugin scripts and styles.
Share on Social Plugin enqueues couple of scripts and a style sheet in
Sos_Frontend::add_sos_scripts()
method.
share-on-social/frontend/class-frontend.php
function add_sos_scripts () {
....
wp_register_style( 'sos_style', SOS_URL . '/css/style.css' );
wp_register_script( 'sos_script', SOS_URL . '/js/share.js',
array('jquery'), '', true );
if ( has_shortcode( $post->post_content, $this->sos_shortcode ) ) {
wp_enqueue_style( 'sos_style' );
wp_enqueue_script( 'sos_script' );
....
}
}
The method, registers the script and stylesheets and if, share-on-social shortcode exists in the post, then it enqueue them. WordPress functions wp_script_is() and wp_style_is() are useful to check whether scripts and stylesheets are registered or enqueued.
share-on-social/tests/phpunit/tests/test-frontend.php
public function test_add_sos_scripts () {
// if no shortcode
$this->frontend->add_sos_scripts();
$this->assertTrue( wp_style_is( 'sos_style', 'registered' ) );
$this->assertTrue( wp_script_is( 'sos_script', 'registered' ) );
$this->assertFalse( wp_style_is( 'sos_style', 'enqueued' ) );
$this->assertFalse( wp_script_is( 'sos_script', 'enqueued' ) );
// if shortcode exists
$this->frontend->setup();
$this->frontend->add_sos_scripts();
$this->assertTrue( wp_style_is( 'sos_style', 'enqueued' ) );
$this->assertTrue( wp_script_is( 'sos_script', 'enqueued' ) );
}
The test calls method under test Sos_Frontend::add_sos_scripts()
and
then, assert that script and stylesheet are registered but not enqueued.
Next, it calls Sos_Frontend::setup()
to register the shortcode and
assert script and stylesheet are enqueued.
Sos_Frontend::add_sos_scripts()
enqueues scripts only if post contains
share-on-social shortcode. It is important to understand, from where it
obtains the post, within tests, that has the shortcode. For that, we
need to look at Test_Frontend::setUp()
method where the global post is
set.
share-on-social/tests/phpunit/tests/test-frontend.php
class Test_Frontend extends WP_UnitTestCase {
....
public function setUp () {
parent::setUp();
global $post;
$this->create_posts();
$post = get_post( $this->child_post_id );
setup_postdata( $post );
....
}
private function create_posts () {
$args = array(
'post_name' => 'parent test page',
'post_title' => 'parent test page title',
'post_status' => 'publish',
'post_type' => 'page',
'post_content' => '<a href="http://codex.wordpress.org/Function_Reference/setup_postdata" target="_blank">[share-on-social][/share-on-social]]'
);
$post_id = $this->factory->post->create( $args );
$this->parent_post_id = $post_id;
$args = array(
'post_name' => 'child test page',
'post_title' => 'child test page title',
'post_status' => 'publish',
'post_type' => 'page',
'post_parent' => $this->parent_post_id,
'post_content' => '[[share-on-social]test-content[/share-on-social]]'
);
$post_id = $this->factory->post->create( $args );
$this->child_post_id = $post_id;
}
In Test_Frontend::setUp()
we call a private method
Test_Frontend::create_posts()
to create two posts - actually two pages
and one of which is the parent of other. The args[‘post_content’] of
these pages holds the share-on-social shortcode. We use factory
method of WP_UnitTestCase
to create the posts.
WordPress holds the current post in the global variable $post. We set the child page as current post by assigning it to the global $post and then, update other global variables related to current post, by calling WordPress function [setup_postdata() . In the test, when add_sos_scripts() is called, it obtains the current post from global $post and checks whether it has the shortcode or not and enqueue scripts accordingly.
Factory Methods
WordPress Test Library class WP_UnitTestCase
comes with a set of
factory methods to create WordPress objects such as Post, Attachment,
Comment, User, Term, Category and Tag.
For example, in a test case, we use following construct to create a post object using factory method..
$args = array(
'post_name' => 'parent test page',
'post_title' => 'parent test page title',
'post_status' => 'publish',
'post_type' => 'page',
'post_content' => '[[share-on-social][/share-on-social]]'
);
$post_id = $this->factory->post->create( $args );
Similarly, comments can be created through $this->factory->comment->create( $args ) with appropriate values in args array.
Factory offers following methods for each type.
create_object ( $args ) - creates an object.
update_object ( $user_id, $fields ) - updates an object.
get_object_by_id ( $user_id ) - fetch an object.
Apart from these common methods, factory also has additional methods for
some of the types. To know more about the factory, browse the source
file share-on-social/tests/phpunit/includes/factory.php
The next blog explains the unit tests for methods which uses WordPress global variables and also, simulate WordPress admin screens to carry out tests related to admin modules.