Unit Testing WordPress Plugins with PHPUnit

Unit Testing WordPress Plugins with PHPUnit

When you’re working on plugins and you start introducing new functionality, it’s good practice to ensure the existing logic isn’t broken. Without proper testing, the chances are high that simple fixes may snowball into a spaghetti coding nightmare.

So in today’s article, I’ll show you how to take advantage of unit testing for your WordPress plugins. While there are many testing frameworks out there, we’ll stick with PHPUnit as it’s the official framework of choice for WordPress.

WordPress unit tests using PHPUnit are generally geared towards plugins, but there may be times when you’d want to use them for themes. However, as a general rule, your theme shouldn’t offer plugin-like functionality.

We’ll briefly look at the setup, characteristics of unit tests in WordPress and, finally, test a simple plugin.

Note: This article is for advanced WordPress developers. However, if you are a beginner or intermediate user, the concepts discussed here may help you understand what goes on behind the scenes when creating a WordPress application.

You will require a working knowledge of WordPress, PHP, and PHPUnit before diving into this post. If you’re unsure or would like a refresher I recommend you read through the following:

Unit Testing: Are We On the Same Page?

More than anything, unit testing is about improving the quality of your software. The goal is to reduce bugs using a systematic approach. You save time and effort by running test suites for different areas of your application and can be confident about your changes.

From a technical perspective, it’s about writing additional code to test your functions or modules in isolation. It involves passing in input data for different scenarios, boundary conditions and ensuring that the output or returned data is always as expected. The function under test is cut off from the rest of the system. It is different from an integration test where we test how components or functions work together. When you introduce new changes, you only need to run your previous tests. What worked before should continue to work and you’ll know immediately if it didn’t.

How this helps you the developer

The secret behind successful unit tests is to write smaller, less complex functions or modules in your main application. This makes it possible to test particular components in isolation. Each function should perform a single task and perform it well. This way you neither hack your way through with several conditional statements nor end up with spaghetti code. You certainly end up writing more code, but code that is more readable and of a higher quality. And it’s inline with the DRY (Don’t Repeat Yourself) principle of coding:

“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”

Now that we’re on the same page, let’s get started.

Prerequisites

To get started, you’ll need three things:

  • PHPUnit – A library to test PHP code
  • WP-CLI – The WordPress Command Line interface to integrate your tests with a WordPress install
  • A WordPress setup.

I recommend you use a development environment like VVV as setting up the above will take a while and you don’t want to bloat your everyday work environment. Check out our post Setting Up VVV for WordPress Development for details on how to use VVV.

A note for Windows users:

There’s no problem with installing Vagrant and VVV on Windows. And if you’re on Microsoft Windows 10 you can kick it up a notch – Microsoft released the Windows Subsystem for Linux (WSL), so you can now have the Ubuntu BASH on Windows. This will allow you to run (almost) all native Linux commands on Windows as well as interact with files on your Windows system. MAC and Linux users always had the shell integrated.

I’ll be using the WSL just to prove that it really works and you no longer need to install a separate VM to get the shell. Here’s how you can set up WSL.

Development Workflow

I am using NetBeans with Vagrant and VVV on the Windows Subsystem for Linux. This gives me a great workflow where I can develop and debug with NetBeans on my host system, and run tests and servers using the VVV virtual environment.

Everything is auto synced between the host and the guest systems. So any tests that I write using NetBeans or any editor in my host are synced to the VM as well.

I also keep my plugins and themes outside the WordPress setups and load them in the target VVV WordPress instance using the CustomFile configuration in Vagrant.

This allows me to use Git (version control) without having to sync unnecessary WordPress core files.

Here’s what my setup looks like:

VVV folder structure

Plugins and themes are in a folder outside WordPress (on the host) and are loaded in the target WordPress install in the VM by using the following CustomFile configuration:

Vagrant CustomFile

I’ll be working with a simple plugin, phpunit-demo-plugin, and a WordPress instance “test.dev” created using vv create to perform unit tests.

Also, to load the test.dev project in NetBeans along with the plugin outside WordPress, I created a symbolic link to the plugins folder as below:

Note: You need to create symbolic links from within the host machine:

  • For Linux/MAC use ln -s target link_name
  • For Windows use command line to run mklink /D link_name target
Netbeans setup

Setting up PHPUnit for Your WordPress Plugin

With VVV we have all the necessary tools: PHP, PHPUnit, WP-CLI etc. WP-CLI also installs a base configuration for the testing framework, which is useful to run tests on the WordPress core files.

To extend this for plugins requires a few additional steps. It boils down to three commands:

  • >$cd /srv/www/wordpress-installation
  • >$wp scaffold plugin-tests your_plugin
  • >$sh  wp-content/plugins/your_plugin/bin/install-wp-tests.sh  <db_args>

PHPUnit will overwrite the WordPress database so it’s best to use a fresh installation or create a backup of your existing one. Needless to say, don’t run it in your production environment.

I’ll refer to folders as directories when working in the Linux VM.

Let’s walk through this in detail. You can also refer to the WP-CLI handbook.

Step#1

To set up PHPUnit with your WordPress plugin, start your Vagrant instance with vagrant up and then ssh into it with vagrant ssh. Once you’re in, navigate to the root directory of your WordPress installation.

In my case, I’m working with the WordPress installation test.dev

>$cd /srv/www/test/htdocs/

Navigate to WordPress install

Step#2

Your plugin (from your host machine) should already be available in the wp-contents directory. To configure unit tests for the plugin, we need to use the WP-CLI’s scaffold command as below:

wp scaffold plugin-tests your_plugin

>$wp scaffold plugin-tests phpunit-demo-plugin

This is what just happened:

Cat test sample

If you now check your plugin’s directory, you’ll notice a few additional files, the most important being in the test subdirectory. PHPUnit will automatically run any tests (files prefixed with test-) it finds under the tests directory. The auto discovery happens through the phpunit.xml, which is the main manifest file that tells PHPUnit where to find the tests and how things are setup.

PHP Unit XML

Step#3

We now only need to set up a testing copy of the database for our plugin. When tests are run, PHPUnit will use this for our test environment. If you wish to use a working db instance, be sure to back it up.

We need to execute the install-wp-tests.sh script located under “bin/” that was also created by the wp scaffold command:

bash plugin_dir/bin/install-wp-tests.sh db_name db_user db_password db_host version

  • db_name is the name of the test database (all data will be deleted!)
  • db_user is the MySQL user name
  • db_pass  is the MySQL user password
  • db_host is the MySQL server host
  • Version is the WordPress version

>$bash bin/install-wp-tests.sh wordpress_test root root localhost latest

Install WP tests

Step#4

To test if everything installed correctly, all you need to do is run the command phpunit. A sample test file test-sample.php that was created earlier will be executed.

>$phpunit

Writing Unit Tests for WordPress

If you look at the included test-sample.php under tests, you’ll notice that the class SampleTest extends WP_UnitTestCase and not PHPUnit_Framework_TestCase. That’s because WordPress ships with its own testing library that offers WordPress specific functionality and is built on top of PHPUnit_Framework_TestCase.

With WP_UnitTestCase, every method that begins with test will be run automatically.

When we ran phpunit above, the test_sample() was executed as it was prefixed with test_ and asserted that True was true.

WP unit test case

Here’s how we can make use of WP_UnitTestCase for our own tests:

WP_UnitTestCase provides us with Object Factories, Utility Methods, and WordPress specific assertions in addition to assertions provided by PHPUnit,

WordPress Test Assertions

  • Assertions for Errors
    • $this->assertWPError($actual, $message)
    • $this->assertNotWPError($actual, $message)
    • $this->assertIXRError($actual, $message)
    • $this->assertNotIXRError($actual, $message)
  • Assertions to test WP_Query for conditional tags
    • $this->assertQueryTrue($args)

E.g. $this->assertQueryTrue('is_single', 'is_feed') means is_single() and is_feed() must be true to pass.

WordPress Object Factories

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

To create a user and get its user id we can simply do –

$user_id = $this->factory->user->create();

Or to create a user with a specific WordPress role

$user_id = $this->factory->user->create( array( 'role' => 'author' ) );

Other factory types include post, attachment, comment, user, term, category, tag, blog, network.

Examples of using Factories

You can find more details about each factory on the WordPress Trac

Additional setUp() and tearDown()

WP_UnitTestCase also provides its own setUp() and tearDown() methods, which can be used with PHPUnit’s setUp() and tearDown(). With tearDown, WP_UnitTestCase will reset the WordPress state that may have changed by a test method. And with setUp it’ll take care of clearing caches and resetting global variables. To use them, simply call them with parent::setUp() or parent::tearDown()

For example:

The WordPress tests run on the root page of your website. To run tests on a different page, you’ll have to instruct it to do so.

For example, to run tests on the Edit Posts Administration screen:

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

For example:

Testing Our Plugin (Finally!)

Let’s use this information and write some tests for our demo plugin. Here’s the link to the phpunit-demo-plugin, which adds an additional user meta item when a new user of type “Editor” is created. It also displays the user meta on the profile screens for administrators and the user to modify.

Start by deleting the test-sample.php from the tests directory, and create a new file, test-phpunit-demo-plugin.php.

Note: I’ll be writing my tests in NetBeans on my host, and will run them with PHPUnit on the VVV instance. All changes will be auto-synced between the two systems. This way I can take advantage of auto completion, code reference, and debugging using Xdebug with NetBeans.

Now let’s write some tests for our plugin. Here we are creating a user with the role of “author”, and checking that the meta key was not created.

When you run the test using phpunit (I’m using phpunit --debug for a detailed output) it should pass:

First unit test

Now, let’s create a failing test by creating a user with a role of “Editor,” and then compare the user meta value for preferred_browser with an empty string.

First failing test code

On running phpunit the test fails.

First failed test phpunit

Note: When an assertion fails, no other tests are run. You can modify this behavior in phpunit.xml.

To fix this, we can either test that the meta value was not empty or perform a string comparison for “chrome”. Also, we’ll split the tests into two functions – to test users with and without an “Editor” role. This way, by separating the assertions, we have a good model to find bugs as they appear.

Passing test metakey chrome

Testing callback or its priority

In our plugin, we added a callback for the user_register action hook with a priority value different from the default one of 10. Here, we can use the has_action function, which returns the priority of the callback on a specified hook.

A failing test as the priority needs to be greater than 10

Failing test callback code

Passing the test with a priority check greater than 10

Testing form submission

To test whether the preferred_browser field is correctly updated from the user’s profile, we’ll have to spoof the POST variable, and then call the method that updates the user meta. This is testing in isolation. We’re only concerned about our plugin’s function that updates the meta, and not with the rest of the system like if the profile form was submitted correctly or not.

A failing test after updating the meta value

Failing test meta update code
Failing test meta update phpunit

A passing test by comparing that preferred_browser was updated to Opera

Passing test meta update phpunit

Here is the complete code for the tests in this post:

Real World Examples of Unit Tests in WordPress

Here are some tests I wrote for a simple admin plugin utility that uses object-oriented constructs.

Note: To take advantage of PHPUnit TestDoubles in a plugin without a class you’ll need to use namespaces, and PHP 5.3 or above.

For some advanced test examples check out:

Testing a WordPress Theme

A simple way to test a WordPress theme is to use Theme Unit Test Data. We’ve got a great article that explains how to set it up.

If your theme provides plugin-like functionality or you want to make use of PHPUnit you can have a look at Tom McFarlin’s Basic Theme tests. How you set it up will depend on the structure of your theme.

Test Driven Development (TDD)

TDD is a software design paradigm where unit tests are written first, and then the code is written to pass the tests. The idea is to write failing unit tests, and then writing code to pass those tests. The cycle continues until all missing functionality has been added. With each iteration, the code is refactored without changing the behavior. Whether you use TDD or not writing tests as you develop is the key here.

Taking Plugin Unit Tests Further

I hope this article helps you get started with PHPUnit tests in WordPress. You could also look at automating your tests using a task runner like gulp or grunt. Finally, you can follow WordPress Trac, which will help you write tests using best practices for WordPress.

Additional Resources

Karan Gupta
I hope you've found this article about plugin testing useful! If you have any questions, about the code, please ask in the comments below!