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:
- WordPress Development for Beginners: Getting Started
- WordPress Development for Beginners: Learning PHP
- WordPress Development for Beginners: Building Themes
- WordPress Development for Beginners: Widgets and Menus
- WordPress Development for Beginners: Building Plugins
- Getting Started with PHPUnit
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.
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.
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:
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:
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
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:
>$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.
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
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:
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.
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_nameis the name of the test database (all data will be deleted!)
db_useris the MySQL user name
db_passis the MySQL user password
db_hostis the MySQL server host
- Version is the WordPress version
>$bash bin/install-wp-tests.sh wordpress_test root root localhost latest
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.
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.
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
- Assertions to test WP_Query for conditional tags
$this->assertQueryTrue('is_single', 'is_feed') means
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
WP_UnitTestCase also provides its own
tearDown() methods, which can be used with PHPUnit’s
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
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
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:
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.
On running phpunit the test fails.
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.
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
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
A passing test by comparing that
preferred_browser was updated to Opera
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:
- Tests for the WordPress core
- WooCommerce Unit Tests
- Tests for ajax requests
- WordPress API Mocking Framework
Testing a WordPress Theme
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.