Unit testing WordPress plugins with PHP8+

When developing plugins for WordPress, it’s useful to run automated tests in a running instance of a website. We can use PHPUnit to build our tests and it’s easy enough to add basic tests to our plugins. But in December 2021, a year after PHP8 was released, the standard setup is still not compatible with the latest PHP versions.

The WordPress CLI Handbook has straightforward instructions on how to add automated testing to our plugins. It tells us how to install PHPUnit and gives us a simple command line that builds a scaffold for unit testing. One niggling problem, however, is that because WordPress still supports some older PHP versions like 5.6 and 7.1, the standard package is built around PHPUnit 7 which doesn’t run on PHP 8.0 and 8.1. This will hopefully change in 2022!

Thankfully, due to the work of Juliette Reinders Folmer and Yoast, today we can use PHPUnit 9 and WordPress 5.9 pre-release (trunk) to run our plugin tests on PHP 8.0 and 8.1. The magic is wrapped up in two packages you can find on GitHub:

The setup

Before we start, we need to already have wp-cli and Composer installed and available to the command line. So do that first!

The initial setup of the testing environment is almost normal except that we want to make sure we get WordPress 5.9+ as our test website. In 2021, that means asking for the nightly version (or trunk) instead of the latest release.

Using wp-cli, we create the test scaffolding from the command line like this:

wp scaffold plugin-tests example-plugin

We then create the test environment:

bin/install-wp-tests.sh dbname dbuser dbpwd localhost nightly

The next step is to install Yoast’s wp-test-utils, which will also install the latest PHPUnit and required polyfills.

composer require --dev yoast/wp-test-utils

One thing to note about the test WordPress environment is that it is created, by default, in the /tmp folder in Linux/MacOS — which means that it is destroyed each time we shutdown the computer.

To simplify matters, I like using a Makefile to run various build / lint / test tasks. Here’s my basic script for setting up and running the test environment — once I’ve installed wp-test-utils and written my tests! To run tests, I can run this command, and it will run the install script if it hasn’t already been run:

make test

# environment variables for tests

export WP_PLUGIN_DIR = $(shell cd ..; pwd)

# run tests

test: /tmp/wordpress-tests-lib
    vendor/bin/phpunit

/tmp/wordpress-tests-lib:
    bin/install-wp-tests.sh dbname dbuser dbpwd localhost nightly

Modifying the bootstrap

Because we’re using wp-test-utils for our test framework instead of the standard WordPress setup, we need to modify our test bootstrap file to suit. The GitHub project has great instructions on how to do this, with two sample bootstrap files that suit different setups.

But let’s cut to the chase and look at what I’m using:

<?php

use Yoast\WPTestUtils\WPIntegration;

$plugin_main_dir = dirname(__DIR__);
$plugin_main_file = "$plugin_main_dir/example-plugin.php";

if (getenv('WP_PLUGIN_DIR') !== false) {
    define('WP_PLUGIN_DIR', getenv('WP_PLUGIN_DIR'));
}

require_once "$plugin_main_dir/vendor/yoast/wp-test-utils/src/WPIntegration/bootstrap-functions.php";

$_tests_dir = WPIntegration\get_path_to_wp_test_dir();

// Give access to tests_add_filter() function.
require_once $_tests_dir . 'includes/functions.php';

/**
 * load our plugin, and any others it requires
 */
tests_add_filter('muplugins_loaded', function() use ($plugin_main_file) {
    require $plugin_main_file;

    // load other plugins we require when testing
    require WP_PLUGIN_DIR . '/gravityforms/gravityforms.php';
});

/*
 * Bootstrap WordPress. This will also load the Composer autoload file, the PHPUnit Polyfills
 * and the custom autoloader for the TestCase and the mock object classes.
 */
WPIntegration\bootstrap_it();

if (!defined('WP_PLUGIN_DIR') || file_exists($plugin_main_file) === false) {
    echo PHP_EOL, 'ERROR: Please check whether the WP_PLUGIN_DIR environment variable is set and set to the correct value. The integration test suite won\'t be able to run without it.', PHP_EOL;
    exit(1);
}

Modifying the test classes

Now that we’re using the Yoast test utils, we need to extend their test case class for our tests, instead of WP_UnitTestCase. Here’s what that looks like.

<?php
namespace example\example_plugin\tests;

use Yoast\WPTestUtils\BrainMonkey\TestCase;
use example\example_plugin\Plugin;

/**
 * test whether plugin loads correctly
 */
class PluginTest extends TestCase {

    /**
     * can get instance of plugin
     */
    public function test_plugin() {
        $this->assertTrue(Plugin::getInstance() instanceof Plugin);
    }

}

Now we can run our plugin tests in PHP 8.0 and later, which is nice because anything older than that has now stopped getting updates — PHP 7.4 will only receive security patches, so we’d better be testing on PHP 8!

Which reminds me, I’d better add some tests to my free plugins, not just my pro plugins!