Unit Testing for Your WordPress Plugin

This article is also published on https://engineering.wedevs.com/2020/10/08/unit-testing-for-your-wordpress-plugin/

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 object
  • create_and_get() – creates and return the entire object
  • create_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:

  1. We extend WP_Ajax_UnitTestCase class
  2. 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