12.3. WordPress Plugin Simple Unit Tests
In the previous blog, we setup PHPUnit for WordPress Plugin and verified the setup with a Simple Plugin. The rest of the blog explains various types of unit tests we used to test the Share on Social Plugin.
The real hurdle in testing a plugin is to understand the WordPress architecture and write the appropriate tests for it. For better understanding, we selected various types of methods that are typical to a plugin and explain how to test them. The complete set of tests of Share on Social Plugin is available for download from GitHub.
In this blog, we start with PHPUnit Test Case, Setup, Tear down and then explain a couple of basic types of plugin tests.
Test Case, Setup, Tear Down
In Share on Social Plugin, for each class file there is corresponding
test class file in tests/phpunit/tests
directory. The test file is a
regular PHP class which extends WordPress Tests Librarys'
WP_UnitTestCase
which itself extends PHPUnit_Framework_TestCase
.
The test method names are prefixed with test_ as PHPUnit requires that to distinguish the test methods from others in the test case.
In all test classes, we define a variable with class scope that holds
the instance of class under test. In the next snippet, the var $sos
will hold an instance of class Sos
which is the class under test.
The instance of the class under test is normally created in setup()
method.
share-on-social/tests/phpunit/tests/test-sos.php
class Test_Sos extends WP_UnitTestCase {
var $sos;
public function setup () {
parent::setup();
require_once 'admin/class-sos.php';
$this->sos = new Sos();
}
....
}
In the setup()
method, we need to call parent::setup()
before doing
any other thing. Next, we include the class file of class under test
with require_once(). In the above test case, we include
admin/class-sos.php
which defines the class Sos
. Finally, we create
an instance of Sos and assign it to the variable $sos. PHPUnit calls
setup() before running each test and so, for each test we get a fresh
instance of class Sos.
After completion of each test, PHPUnit calls teardown()
, where we call
parent::teardown()
and then any clean up required to run subsequent
tests.
share-on-social/tests/phpunit/tests/test-sos.php
class Test_Sos extends WP_UnitTestCase {
var $sos;
....
public function teardown () {
parent::teardown();
unload_textdomain( 'sos-domain' );
Util::set_activate_plugins_cap( false );
}
....
}
In majority of test cases, in teardown()
method, we unload the text
domain which we explain in a later blog when we cover the WordPress
Internationalization testing. In the above test case, we also remove a
user capability which is enabled by some test in this particular test
case.
The things we do in setup() and teardown() methods varies for each test case, but they are easy to understand once you go through test class.
Methods with Return Value
The simplest type of plugin tests are of the methods that return some
value. For example, the Sos_Options::sanitize_options()
method takes
an array as input which hold key, value pairs and the method sanitize
the values using WP function wp_filter_nohtml_kses() and add the sanitized value to an output array. The method returns the output array which holds the sanitized values.
share-on-social/admin/class-options.php
function sanitize_options ( $input ) {
$output = array();
foreach ( $input as $key => $val ) {
if ( isset( $input<a href="https://phpunit.de/manual/current/en/appendixes.assertions.html" target="_blank"> $key ] ) ) {
// $output[$key] = strip_tags(stripslashes($input[$key]));
$output[ $key ] = wp_filter_nohtml_kses( $input[ $key ] );
}
}
return apply_filters( 'sanitize_options', $output, $input );
}
We test this type of methods by asserting whether the returned object contains the expected values.
share-on-social/tests/phpunit/tests/test-options.php
public function test_sanitize_options_scripts () {
$input = array(
'id' => 'test id<h1><?php evil ?>'
);
$output = $this->sos_options->sanitize_options( $input );
$this->assertCount( 1, $output );
$this->assertSame( 'test id', $output[ 'id' ] );
}
In the test method, first we construct an input array and add an option
value which contains some PHP script tags. Next, we call method under
test Sos_Options::sanitize_options()
by passing input array as
parameter. Then, we test whether the returned output has only one
element and whether the method has sanitized the value by removing the
PHP tags from the input value. The instance of class Sos_Options
is
instantiated in test case setup() method and assigned to the variable
$this->sos_options.
Always Count
When testing an array, always assert the size of the array with assertCount(). It ensures that test fails whenever you make changes to the code that affects the size of the array, and failure alerts you to write additional assertions to test the changes.
Methods that Echo Output
One more type of frequently used methods are those which echo output without return value. Plugin uses such methods as callbacks to render pages, menu etc.,
One such example is Sos_Stats::render_stats_page()
method from
admin/class-stats.php
.
share-on-social/admin/class-stats.php
public function render_stats_page () {
$heading = __( 'Share Stats', 'sos-domain' );
echo <<<EOD
<h3>{$heading}</h3>
<div> </div>
<div id="summary_chart"></div>
<div> </div>
<div id="stats_chart"></div>
EOD;
}
In Share on Social, we test such methods by capturing the echoed output with PHP output buffer functions.
share-on-social/tests/phpunit/tests/test-stats.php
public function test_render_stats_page () {
ob_start();
$this->sos_stats->render_stats_page();
$result = ob_get_contents();
ob_end_clean();
$expected = <<< EOD
<h3>Share Stats</h3>
<div> </div>
<div id="summary_chart"></div>
<div> </div>
<div id="stats_chart"></div>
EOD;
$this->assertSame( trim( $expected ), trim( $result ) );
}
PHP output buffer functions are used to capture the output. After turning on the output buffer with PHP function ob_start(), we call the method under test. The echoed output is obtained with ob_get_contents() and after that, buffer is turned off with ob_end_clean(). The result is asserted for desired value.
Multiple Assertions
Unit Test best practice recommends to have single assertion per test which ensures that tests are concise and readable. When test fails, developers can easily pinpoint the error and refactor the code.
But in Share on Social Plugin test cases we have not followed this recommendation. Single assert per test rule results in too many tests and it is bit difficult to browse the test code and comprehend. Instead, we use single test to test single condition by making multiple assertions to test that condition.
If you don’t mind too many test methods and have knack to meaningfully name them, then it is better to stick with the single assertion per test rule.
In the next blog, we explain some of the advanced types of tests that are unique to WordPress Plugin.