Unit testing is one of the software testing types which includes the initial testing phase where the smallest components or the modules of a software are tested individually. If you want to reduce your WordPress plugin bugs from production then this is the technic that will help you most. So you have a plugin and you want to test it using PHPUnit.
Prerequisite
You must have installed WP-CLI and Composer, If you don’t have installed WP-CLI then go to the WP-CLI installation page and install it. If you don’t have installed Composer then go to the Composer installation page and install it.
Setup Local Environment
Go to your plugin directory and run the following command. Replace my-plugin
with your plugin slug.
wp scaffold plugin-tests my-plugin
This command will generate two folders bin
and tests
, also it will create three files .phpcs.xml.dist
, .travis.yml
and phpunit.xml.dist
in your plugin. Now rename phpunit.xml.dist
to phpunit.xml
In order to initialize the testing environment locally, run the install script. Make sure you have installed wget
.
bash bin/install-wp-tests.sh wordpress_test root '' localhost latest
The install script installs a copy of WordPress in the /tmp
directory as well as the WordPress unit testing tools. Then it creates a database to be used while running tests. The parameters are:wordpress_test
is the name of the test database. Make sure it is different from your WordPress installation database because all data will be deleted.root
is the MySQL user name.''
is the MySQL user password.localhost
is the MySQL server host.latest
is the WordPress version. You may also use other versions like 4.9, 5.0, 5.2 etc.
Now add composer.json
file on your project.
{
"require-dev": {
"phpunit/phpunit": "^6"
}
}
After adding composer.json
file, install composer packages.
composer install
Now you are ready to go. Run the command that checks everything is correctly set up or not.
vendor/bin/phpunit
This command will run all tests from our plugin, Though right now we don’t have any test written.
WordPress Testing Ecosystem
WordPress ships with its own testing library that offers WordPress specific functionality and it is built on top of PHPUnit_Framework_TestCase
class. We will extend class WP_UnitTestCase
to write our own test case. Every method that begins with test
will be run automatically.
WP_UnitTestCase
provides us with Object Factories, Utility Methods, and WordPress specific assertions.
WordPress Test Assertions
$this->assertWPError($actual, $message)
$this->assertNotWPError($actual, $message)
$this->assertIXRError($actual, $message)
$this->assertNotIXRError($actual, $message)
$this->assertQueryTrue($args)
$this->assertEqualSets($expected, $actual)
WordPress Object Factories
WordPress Factories make it very simple to create posts, taxonomies, users, etc. They use the following three methods to create objects:
create()
– returns the object ID of the created objectcreate_and_get()
– creates and return the entire objectcreate_many($count)
– creates multiple posts based on $count
List Factory Objects
We will have access following objects from WP_UnitTestCase
class. You can find more details about each factory on the WordPress Trac.
- $this->factory->post
- $this->factory->attachment
- $this->factory->comment
- $this->factory->user
- $this->factory->term
- $this->factory->category
- $this->factory->tag
- $this->factory->bookmark
For multisite
- $this->factory->blog
- $this->factory->network
setUp and tearDown
In PHPUnit testing we have the setUp()
method that is called before every test. And we have the tearDown()
method that is called after every test.
WP_UnitTestCase
also provides its own setUp()
and tearDown()
methods, which can be used with PHPUnit’s setUp()
and tearDown()
. To use them, simply call them with parent::setUp()
or parent::tearDown()
. For example:
<?php
class SampleTest extends WP_UnitTestCase {
public function setUp() {
parent::setUp();
// Your own code.
}
public function tearDown() () {
parent::tearDown();
// Your own code.
}
// Rest of the code.
}
Test on WordPress Pages
The WordPress tests run on the root page of your website. To run tests on the Edit Posts Administration screen:
<?php
class SampleTest extends WP_UnitTestCase {
public function setUp() {
parent::setUp();
set_current_screen('edit.php');
// this will cause is_admin to return true.
$this->assertTrue( is_admin() );
}
// Rest of the code.
}
If you want to test by navigating to a specific URL you can make use of $this->go_to($url)
, and then test with assertQueryTrue($args)
. For example:
<?php
class SampleTest extends WP_UnitTestCase {
public function test_some_page() {
$this->go_to( '/' );
$this->assertQueryTrue ( 'is_home', 'is_front_page' );
}
// Rest of the code.
}
Writing Test Case
In our plugin, we have a function which creates some user meta and returns a boolean value, and another function which returns the saved user meta. To test these functions first create a file tests/test-user-info.php
and add the codes written below.
<?php
class User_Info_Test extends WP_UnitTestCase {
private $user_id;
public function setUp() {
parent::setUp();
$this->user_id = $this->factory->user->create();
}
public function test_user_all_info() {
$data = array(
'phone' => '01630634726',
'city' => 'Dhaka',
'country' => 'Bangladesh',
);
$result = ut_set_user_information( $this->user_id, $data );
$this->assertTrue( $result );
$info = ut_get_user_information( $this->user_id );
$this->assertEquals( $data, $info );
}
}
Here we have learned to write a test case that can test any functions. Now we will learn how to test WordPress AJAX requests. To do so here I have created a new file tests/test-ajax-request.php
<?php
class Ajax_Request_Test extends WP_Ajax_UnitTestCase {
public function test_create_post_by_ajax() {
$_POST['_wpnonce'] = wp_create_nonce( 'ut_create_book_post' );
$_POST['post_title'] = 'This is post title';
$_POST['post_content'] = 'This is post content';
try {
$this->_handleAjax( 'ut_create_book_post' );
} catch ( Exception $e ) {
// We expected this, do nothing.
}
$response = json_decode( $this->_last_response );
$post = get_post( $response->data->post_id );
$this->assertEquals( 'WP_Post', get_class( $post ) );
$this->assertEquals( $_POST['post_title'], $post->post_title );
}
}
Two important parts here:
- We extend
WP_Ajax_UnitTestCase
class ut_create_book_post
is the ajax action
To test WordPress REST API we need two additional helper method, I’m adding them in my test class. I have created a new file tests/test-user-info-api.php
to test REST endpoints.
class User_Info_API_Test extends WP_UnitTestCase {
private $user_id;
public function setUp() {
parent::setUp();
$this->user_id = $this->factory->user->create();
}
public function test_store_user_info() {
$data = array(
'phone' => '01630634726',
'city' => 'Dhaka',
'country' => 'Bangladesh',
'user_id' => $this->user_id,
);
$response = $this->request_with_params( '/ut/v1/user-meta', $data, 'POST' );
$this->assertEquals( 201, $response->get_status() );
unset( $data['user_id'] );
$this->assertEqualSets( $data, $response->get_data() );
}
private function request_with_params( $path, $params, $method ) {
$request = $this->get_request( $path, $params, $method );
return rest_get_server()->dispatch( $request );
}
private function get_request( $path, $params, $method ) {
$request = new WP_REST_Request( $method, $path );
foreach ( $params as $param => $value ) {
$request->set_param( $param, $value );
}
return $request;
}
}
Here request_with_params
and get_request
is the self-made helper functions that help us to test REST endpoints.
Enable deprecated expectations on testing. Sometime we may need to show the expectations then override expectDeprecated
method
class User_Info_API_Test extends WP_UnitTestCase {
/**
* Sets up the expectations for testing a deprecated call.
*/
public function expectDeprecated() {
return true;
}
}
References
- The various assertion methods that are available will be found here.
- WordPress core test example will be found here.
- Gutenberg test example will be found here.
- Demo plugin: https://github.com/sourovroy/wp-plugin-unit-test
One reply on “Unit Testing for Your WordPress Plugin”
Thanks! Really helpful.