Post Reads 22 reads

In “WordPress PHPUnit Tests” definitive guide, I am going to explains all about the PHP unit testing in WordPress.

I try to cover all the possible topics in this guide including setting up PHP unit test environment, writing tests, etc.

In this guide you are going to learn:

Create a sample plugin Create a sample plugin

Instead of of existing plugin, Lets create a new plugin from a scratch and implement the PHP unit test cases within it.

Step 1: Create a empty directory sample-plugin

Step 2: Create a new file sample-plugin.php

Step 3: Copy and paste below below code within it.

<?php
/**
 * Plugin name: Sample Plugin
 */

Step 4: That’s it. We have created a new plugin. See it into the plugins list screen as below:

Install the PHPUnit files and folders Install the PHPUnit files and folders

WordPress provide the PHPUnit test files for us. So, We just need to use it.

We need to use the command wp scaffold plugin-tests to setup the WordPress PHPUnit test cases for our plugin.

Follow below steps:

Step 1: Open the terminal or command prompt (CMD)

Step 2: Navigate to \wp-content\plugins\sample-plugin directory.

Step 3: Execute command wp scaffold plugin-tests sample-plugin

E.g.

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ wp scaffold plugin-tests sample-plugin
Success: Created test files.

Step 4: Now, you can see the PHP Unit files

.phpcs.xml.dist  
.travis.yml      
phpunit.xml.dist 
├───bin
├──────install-wp-tests.sh
├───tests
└──────bootstrap.php
└──────test-sample.php

Here, We have a all the file structure to execute the unit tests.

Setup Test WordPRess Setup Test WordPRess

Note: If you already have a WordPress test setup for executing the PHPUnit tests then you can skip this section.

The test WordPress setup is installed into the temp directory. To setup the test WordPress instance.

No worry. With the scaffold command we have a install-wp-tests.sh file within the bin directory.

├───bin
├──────install-wp-tests.sh

The install-wp-tests.sh create us the test WordPress setup.

So, lets execute it.

bash bin/install-wp-tests.sh

e.g.

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ bash bin/install-wp-tests.sh
usage: bin/install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]

Here, We are getting the error to provide additional information to create a test WordPress setup.

Syntax:

bash bin/install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]

Here we need the database, username, password, host. Other fields are optional.

Create a test database. I’m using a Xampp so, I’m going to create a database from http://localhost/phpmyadmin/.

Screenshot:

Here, I have created a new database wordpress_test.

Now, We have a database to create a test WordPress site.

Lets execute the command by passing the database, username, password, host as below.

E.g.

bash bin/install-wp-tests.sh wordpress_test root '' localhost

After executing the command you can see somehting like below:

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ bash bin/install-wp-tests.sh
usage: bin/install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ bash bin/install-wp-tests.sh wordpress_test root '' localhost
+ install_wp
+ '[' -d /tmp/wordpress/ ']'
+ mkdir -p /tmp/wordpress/
+ [[ latest == \n\i\g\h\t\l\y ]]
+ [[ latest == \t\r\u\n\k ]]
+ '[' latest == latest ']'
+ local ARCHIVE_NAME=latest
+ download https://wordpress.org/latest.tar.gz /tmp/wordpress.tar.gz
++ which curl
+ '[' /mingw64/bin/curl ']'
+ curl -s https://wordpress.org/latest.tar.gz

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ bash bin/install-wp-tests.sh wordpress_test root '' localhost
+ install_wp
+ '[' -d /tmp/wordpress/ ']'
+ mkdir -p /tmp/wordpress/
+ [[ latest == \n\i\g\h\t\l\y ]]
+ [[ latest == \t\r\u\n\k ]]
+ '[' latest == latest ']'
+ local ARCHIVE_NAME=latest
+ download https://wordpress.org/latest.tar.gz /tmp/wordpress.tar.gz
++ which curl
+ '[' /mingw64/bin/curl ']'
+ curl -s https://wordpress.org/latest.tar.gz

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ bash bin/install-wp-tests.sh wordpress_test root '' localhost
+ install_wp
+ '[' -d /tmp/wordpress/ ']'
+ mkdir -p /tmp/wordpress/
+ [[ latest == \n\i\g\h\t\l\y ]]
+ [[ latest == \t\r\u\n\k ]]
+ '[' latest == latest ']'
+ local ARCHIVE_NAME=latest
+ download https://wordpress.org/latest.tar.gz /tmp/wordpress.tar.gz
++ which curl
+ '[' /mingw64/bin/curl ']'
+ curl -s https://wordpress.org/latest.tar.gz
+ tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C /tmp/wordpress/
+ download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php /tmp/wordpress//wp-content/db.php
++ which curl
+ '[' /mingw64/bin/curl ']'
+ curl -s https://raw.github.com/markoheijnen/wp-mysqli/master/db.php
+ install_test_suite
++ uname -s
+ [[ MSYS_NT-6.1-7601 == \D\a\r\w\i\n ]]
+ local ioption=-i
+ '[' '!' -d /tmp/wordpress-tests-lib ']'
+ '[' '!' -f wp-tests-config.php ']'
+ download https://develop.svn.wordpress.org/tags/5.4.2/wp-tests-config-sample.php /tmp/wordpress-tests-lib/wp-tests-config.php
++ which curl
+ '[' /mingw64/bin/curl ']'
+ curl -s https://develop.svn.wordpress.org/tags/5.4.2/wp-tests-config-sample.php
++ echo /tmp/wordpress/
++ sed 's:/\+$::'
+ WP_CORE_DIR=/tmp/wordpress
+ sed -i 's:dirname( __FILE__ ) . '\''/src/'\'':'\''/tmp/wordpress/'\'':' /tmp/wordpress-tests-lib/wp-tests-config.php
+ sed -i s/youremptytestdbnamehere/wordpress_test/ /tmp/wordpress-tests-lib/wp-tests-config.php
+ sed -i s/yourusernamehere/root/ /tmp/wordpress-tests-lib/wp-tests-config.php
+ sed -i s/yourpasswordhere// /tmp/wordpress-tests-lib/wp-tests-config.php
+ sed -i 's|localhost|localhost|' /tmp/wordpress-tests-lib/wp-tests-config.php
+ install_db
+ '[' false = true ']'
+ PARTS=(${DB_HOST//\:/ })
+ local PARTS
+ local DB_HOSTNAME=localhost
+ local DB_SOCK_OR_PORT=
+ local EXTRA=
+ '[' -z localhost ']'
++ echo
++ grep -e '^[0-9]\{1,\}$'
+ '[' ']'
+ '[' -z ']'
+ '[' -z localhost ']'
+ EXTRA=' --host=localhost --protocol=tcp'
+ mysqladmin create wordpress_test --user=root --password= --host=localhost --protocol=tcp

One more thing we need to setup for the Windows user.

You can get something below error while executing the PHP unit tests.

PHP Warning:  require_once(/tmp/wordpress//wp-includes/class-phpmailer.php): failed to open stream: No such file or directory in C:\Users\ADMIN\AppData\Local\Temp\wordpress-tests-lib\includes\mock-mailer.php on line 2
PHP Stack trace:
PHP   1. {main}() C:\xampp\php\phpunit-7.5.20.phar:0
PHP   2. PHPUnit\TextUI\Command::main() C:\xampp\php\phpunit-7.5.20.phar:620
PHP   3. PHPUnit\TextUI\Command->run() phar://C:/xampp/php/phpunit-7.5.20.phar/phpunit/TextUI/Command.php:162
PHP   4. PHPUnit\TextUI\Command->handleArguments() phar://C:/xampp/php/phpunit-7.5.20.phar/phpunit/TextUI/Command.php:173
PHP   5. PHPUnit\TextUI\Command->handleBootstrap() phar://C:/xampp/php/phpunit-7.5.20.phar/phpunit/TextUI/Command.php:863
PHP   6. PHPUnit\Util\FileLoader::checkAndLoad() phar://C:/xampp/php/phpunit-7.5.20.phar/phpunit/TextUI/Command.php:1058
PHP   7. PHPUnit\Util\FileLoader::load() phar://C:/xampp/php/phpunit-7.5.20.phar/phpunit/Util/FileLoader.php:45
PHP   8. include_once() phar://C:/xampp/php/phpunit-7.5.20.phar/phpunit/Util/FileLoader.php:57
PHP   9. require() C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin\tests\bootstrap.php:34
PHP  10. require_once() C:\Users\ADMIN\AppData\Local\Temp\wordpress-tests-lib\includes\bootstrap.php:87

Here, I’m getting an error because of the constant ABSPATH absolute path is not set.

This is a known error for the Windows environment.

So, the fix is we need to set the ABSPATH of our WordPress test setup.

Todo this:

  • Open file C:\Users\{YOURNAME}\AppData\Local\Temp\wordpress-tests-lib\wp-tests-config.php
  • Serach for:
    define( 'ABSPATH', '/tmp/wordpress/' );
  • Replace with
    define( 'ABSPATH', 'C:\Users\{YOURNAME}\AppData\Local\Temp/wordpress/' );

Note: Here you need to use your {YOURNAME} with your current system username. My usernmae is ADMIN

So, I have search for define( 'ABSPATH', '/tmp/wordpress/' ); with define( 'ABSPATH', 'C:\Users\ADMIN\AppData\Local\Temp/wordpress/' );

Now, our test site is ready to use for the PHPUnit tests.

Configure the PHPUnit file Configure the PHPUnit file

Create a duplicate copy of phpunit.xml.dist and name it phpunit.xml

Keep the content of the file as it is.

Your phpunit.xml file contain something:

<?xml version="1.0"?>
<phpunit
    bootstrap="tests/bootstrap.php"
    backupGlobals="false"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    >
    <testsuites>
        <testsuite>
            <directory prefix="test-" suffix=".php">./tests/</directory>
            <exclude>./tests/test-sample.php</exclude>
        </testsuite>
    </testsuites>
</phpunit>

Testing PHPUnit Test Testing PHPUnit Test

Now execute the command phpunit into the plugin directoory.

E.g..

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ phpunit
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

  Warning - The configuration file did not pass validation!
  The following problems have been detected:

  Line 11:
  - Element 'testsuite': The attribute 'name' is required but missing.

  Test results may not be as expected.




Time: 13.59 seconds, Memory: 38.00 MB

No tests executed!

Screenshot

Here, The testsuite name is recommend for that we are getting warning:

Element 'testsuite': The attribute 'name' is required but missing.

Lets add a testsuite name.

  • Open the file phpunit.xml
  • replace the <testsuite> with <testsuite name="default">.

Now, Run the command phpunit

E.g.

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ phpunit
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.



Time: 17.48 seconds, Memory: 38.00 MB

No tests executed!

Screenshot – https://i.imgur.com/IMLNYzA.png

Yup! Our PHPunit works as expected.

Here we are getting the output No tests executed! because of we have not any test case yet.

Dont worry. We have a sample test case file /tests/test-sample.php.

To make it work open the file phpunit.xml and remove <exclude>./tests/test-sample.php</exclude>.

Now, Execute the phpunit command.

E..g

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ phpunit
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 23.2 seconds, Memory: 40.00 MB

OK (1 test, 1 assertion)

Screenshot – https://i.imgur.com/Zwwbnbk.png

Here, We gtting the result OK (1 test, 1 assertion).

Our first test case from the file wp-content\plugins\sample-plugin\tests\test-sample.php executed and works as expected.

Lets see what is the test case into the file test-sample.php

See test cases. See test cases.

Open file test-sample.php you can see something:

<?php
/**
 * Class SampleTest
 *
 * @package Sample_Plugin
 */

/**
 * Sample test case.
 */
class SampleTest extends WP_UnitTestCase {

    /**
     * A single example test.
     */
    public function test_sample() {
        // Replace this with some actual testing code.
        $this->assertTrue( true );
    }
}

Here,

  • The PHP class SampleTest extends with WP_UnitTestCase it means that we can use the methods of WP_UnitTestCase into our testcase class SampleTest. We’ll see these methods in next article.
  • We have a method test_sample(). Note that every function need to be prefixed with test_.
  • In the test_sample() function we have a code $this->assertTrue( true );
  • As per its name the function assertTrue() expecte the value should be a true. So, When we execute the PHPUnit test then we get the success message.

Now, Lets make a small change to fail our test case.

Add the falue value within the assertTrue().

E.g. Change $this->assertTrue( true ); with $this->assertTrue( false );

Now, Execute the phpunit command.

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ phpunit
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 21.95 seconds, Memory: 42.00 MB

There was 1 failure:

1) SampleTest::test_sample
Failed asserting that false is true.

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin\tests\test-sample.php:18

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Screenshot https://i.imgur.com/JdjHidU.png

Here, We see that 1 test, 1 assertion, and 1 assertion.

we also see that the function SampleTest::test_sample failed because of error Failed asserting that false is true. on line test-sample.php:18.

Here, We have used a function assertTrue(). Now, lets

Test the dynamic content Test the dynamic content

We can test our functions and provide them some expected inputs. But, Additionally we can test the functionality which is depends on the dynamic content such as posts, pages or custom post types.

We can create the same post, pages and custom post types for our testing.

WordPress provides the PHP base class WP_UnitTest_Factory which some below helper classes to make it work.

  • WP_UnitTest_Factory_For_Post
  • WP_UnitTest_Factory_For_Term
  • WP_UnitTest_Factory_Callback_After_Create
  • WP_UnitTest_Factory_For_Attachment
  • WP_UnitTest_Factory_For_Blog
  • WP_UnitTest_Factory_For_Bookmark
  • WP_UnitTest_Factory_For_Comment
  • WP_UnitTest_Factory_For_Network
  • WP_UnitTest_Factory_For_Thing
  • WP_UnitTest_Factory_For_User

Lets create a simple post and check it has the expected post title and the post type.

The object $this->factory is the object of WP_UnitTest_Factory class which contain:

$this->post       = new WP_UnitTest_Factory_For_Post( $this );
$this->attachment = new WP_UnitTest_Factory_For_Attachment( $this );
$this->comment    = new WP_UnitTest_Factory_For_Comment( $this );
$this->user       = new WP_UnitTest_Factory_For_User( $this );
$this->term       = new WP_UnitTest_Factory_For_Term( $this );
$this->category   = new WP_UnitTest_Factory_For_Term( $this, 'category' );
$this->tag        = new WP_UnitTest_Factory_For_Term( $this, 'post_tag' );
$this->bookmark   = new WP_UnitTest_Factory_For_Bookmark( $this );
if ( is_multisite() ) {
    $this->blog    = new WP_UnitTest_Factory_For_Blog( $this );
    $this->network = new WP_UnitTest_Factory_For_Network( $this );
}

So, The $this->factory->post object which which is the object of Class WP_UnitTest_Factory_For_Post and it is extended from WP_UnitTest_Factory_For_Thing.

We are going to use the $this->factory->post to create a new post.

Lets check it with example. Copy the below code and paste it into the \wp-content\plugins\sample-plugin\tests\test-sample.php file.

/**
 * Create a new post
 */
public function test_create_new_post() {

    $post_id = $this->factory->post->create(
        array(
            'post_title' => 'Hello World',
            'post_type'  => 'post',
        )
    );

    // Check the expected post title.
    $this->assertEquals( 'Hello World', get_the_title( $post_id ) );

    // Check the expected post type.
    $this->assertEquals( 'post', get_post_type( $post_id ) );
}

Lets see what is the above code.

1) We have used $this->factory->post and call a function create() and pass the parameters

array(
    'post_title' => 'Hello World',
    'post_type'  => 'post',
)

These are the post parameters. Here the new post is created with the title Hello World

2) We have used two assertEquals() functions. The first one for to test the title of the post and second to test the post type.

a) Testing the post title.

$this->assertEquals( 'Hello World', get_the_title( $post_id ) );

Here we have pass the first parameter Hello World and second parameter get_the_title( $post_id ).

The function $this->factory->post->create() return the $post_id.

So, We can use The function get_the_title() to get the post Id of the provided post ID.

The function return the Hello World so our case will pass.

b) Testing the post type

$this->assertEquals( 'post', get_post_type( $post_id ) );

Same as post title we have test the post type of the post with the post ID.

Here we have pass the first parameter post and second get_the_title( $post_id ) which return the post type.

This tast case also pass.

Refer the complete code as below.

<?php
/**
 * Class SampleTest
 *
 * @package Sample_Plugin
 */

/**
 * Sample test case.
 */
class SampleTest extends WP_UnitTestCase {

    /**
     * A single example test.
     */
    public function test_sample() {
        // Replace this with some actual testing code.
        $this->assertTrue( true );
    }

    /**
     * Create a new post
     */
    public function test_create_new_post() {

        $post_id = $this->factory->post->create(
            array(
                'post_title' => 'Hello World',
                'post_type'  => 'post',
            )
        );

        // Check the expected post title.
        $this->assertEquals( 'Hello World', get_the_title( $post_id ) );

        // Check the expected post type.
        $this->assertEquals( 'post', get_post_type( $post_id ) );

    }
}

Lets execute the phpunit command.

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ phpunit
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 15.26 seconds, Memory: 38.00 MB

OK (2 tests, 3 assertions)

Screenshot –

Obviously the function get_the_title() and get_post_type() return the actual stored values.

Now, Lets think that our plugin Sample Plugin use the filter the_title to modify the post title.

Copy below code and paste into the file wp-content\plugins\sample-plugin\sample-plugin.php

<?php
/**
 * Plugin name: Sample Plugin
 */

add_filter( 'the_title', function( $post_title = '' ) {
    return 'Prefix ' . $post_title;
});

Here, I have used filter the_title which add the string Prefix at the start of the post title.

Now, Lets execute our phpunit command.

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ phpunit
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 15.87 seconds, Memory: 38.00 MB

There was 1 failure:

1) SampleTest::test_create_new_post
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'Hello World'
+'Prefix Hello World'

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin\tests\test-sample.php:34

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

Here, Out PHPUnit test case failed. Becuase, Our plugin filter the post title and concate the string Prefix at the start of the post tile.

So, Our test case is failed.

Now, To make our test case work as expected.

Change the string:

$this->assertEquals( 'Hello World', get_the_title( $post_id ) );

With

$this->assertEquals( 'Prefix Hello World', get_the_title( $post_id ) );

And then execute the phpunit

C:\xampp\htdocs\dev\wp-content\plugins\sample-plugin
λ phpunit
Installing...
Running as single site... To run multisite, use -c tests/phpunit/multisite.xml
Not running ajax tests. To execute these, use --group ajax.
Not running ms-files tests. To execute these, use --group ms-files.
Not running external-http tests. To execute these, use --group external-http.
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 16.01 seconds, Memory: 38.00 MB

OK (2 tests, 3 assertions)

Screesnhot

Now, Our PHP unit test succeed.

Let’s try to implement PHP unit test for your plugin.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top
%d bloggers like this: