In the “WordPress PHPUnit Tests” definitive guide, I am going to explains all about the PHP unit testing in WordPress.
I try to cover all the everything in this guide including setting up PHP unit test environment, writing tests, etc.
In this guide you are going to learn:
How PHPUnit test works? How PHPUnit test works?
Basic Concept Basic Concept
To understand how PHPUnit works, Lets see one small example:
function add( $a, $b ) { return $a + $b; }
In above function if we use add( 10, 20 ) then it need to return the 30.
We know it. But, what if we try add( ‘one’, 20 ) then it should not work as expected.
So, PHPUnit tests helps to avoid any mistakes and also improve our business logic with all possible cases.
A simple unit test for above code will be:
$this->assertEquals( 30, add( 10, 20 ) );
For now avoid $this we’ll see it in below section.
Here, We have used assertEquals (It is a one of the assertion method from PHPUnit library) to test the add() function return the expected output from the given input.
Unit tests can be anything as per our requirement.
Hope you have clear idea about the PHPUnit tests.
Now, I also want to let you know that, To execute the PHPUnit test cases we need a WordPress test site.
PHPUnit in WordPress PHPUnit in WordPress
Yes. A WordPress test site in which we perform our test cases. WordPress PHPUnit test helper classes allow us to create post, user, page, or anything in our test site to perform actual tests.
Suppose, Our plugin allow user to add a meta field only for the author user role.
We can programmatically create the user with author user role and perform our tests.
We can also test cases on WordPress multisite.
So, Lets begin.
Advertisement
[ad1]
Create a Sample Plugin Create a Sample Plugin
Note: You can skip this step if you want to setup PHPUnit tests for your existing plugin or theme.
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:
Advertisement
[ad2]
Install PHPUnit Install PHPUnit
WordPress provides the WP CLI command wp scaffold plugin-tests to setup the PHPUnit test required files and folders.
Follow below steps to setup the PHPUnit for our sample plugin (Or you can execute it into your own plugin):
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.
Advertisement
[ad3]
Create WordPress Test Site Create WordPress Test Site
Note: If you already have a WordPress test setup for executing the PHPUnit tests then you can skip this section.
We have setup the required files and folders into our plugin.
Now, We need the sample WordPress site to execute our test cases.
Yes. The PHPUnit tests are
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 something 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.
Advertisement
[ad4]
Configure PHPUnit file Configure 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>
Advertisement
[ad5]
Run PHPUnit Run PHPUnit
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
Advertisement
[ad6]
Examples Examples
Example 1: Simple Test Example 1: Simple Test
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 atrue
. 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().
Advertisement
[ad7]
Example 2: Test Dynamic Content Example 2: Test 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
Advertisement
[ad8]
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.
Advertisement
[ad9]
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.
Advertisement
[ad10]
I am using in windows 10, the following command bash bin/install-wp-tests.sh mydb-test root ” localhost. But for some reason I am not getting the wordpress folders and files in Temp folder.
I see few are stuck with same question, but I do not see any workaround. Any suggestion will help. Thank you
I am using in windows 10, the following command bash bin/install-wp-tests.sh mydb-test root ” localhost. But for some reason I am not getting the wordpress folders and files in Temp folder.
I see few are stuck with same question, but I do not see any workaround. Any suggestion will help. Thank you