Before you can test a single permission_callback, you need WP_UnitTestCase
available, and that means the WordPress core test library has to exist somewhere
PHPUnit can load it. There are two ways to get it: an SVN checkout script that’s been
the standard approach for over a decade, or a Composer package that installs the same
library as a normal dependency, versioned and reproducible the way the rest of your
composer.json already is. This lesson sets up the second approach, since it’s the one
that plays best with a CI pipeline you don’t want depending on a shell script and a
live SVN mirror.
A local WordPress environment with WP-CLI available, Composer installed, and a real
MySQL database you can point tests at (a fresh, disposable one, WP_UnitTestCase
creates and tears down real tables). PHP 8.1 or newer assumed for the code in this
lesson.
Step 1: Scaffold the starting files
WP-CLI’s scaffold plugin-tests command generates the standard starting structure,
you don’t want to hand-write this from scratch:
# File: terminal
wp scaffold plugin-tests my-plugin --ci=github
This produces tests/bootstrap.php, phpunit.xml.dist, tests/test-sample.php,
bin/install-wp-tests.sh, and a starter .github/workflows/phpunit.yml. The
install-wp-tests.sh script is the classic SVN-based approach, it works, but it
means your CI pipeline is checking out the WordPress core test suite from source on
every run rather than resolving it as a pinned Composer dependency. Keep the scaffold’s
other files, you’re about to replace how the test library itself gets installed.
Step 2: Require the Composer-based test library
# File: terminal
composer require --dev wp-phpunit/wp-phpunit yoast/phpunit-polyfills
wp-phpunit/wp-phpunit is a real, actively maintained Packagist package built
directly from WordPress core’s own develop repository, git://develop.git.wordpress.org,
so it stays in sync with WordPress core’s own test suite rather than being a
reimplementation of it. yoast/phpunit-polyfills is a required companion, not an
optional convenience: WordPress core’s own PHPUnit infrastructure has depended on it
since WordPress 5.8.2, and unlike a normal transitive dependency it isn’t installed
automatically for you, wp-phpunit/wp-phpunit’s own Composer metadata doesn’t declare
it as a dependency, so skip this line and your tests fail with confusing autoloading
errors that have nothing obviously to do with a missing package.
Because the package is a versioned mirror of WordPress core, you can install an exact
matching version, composer require --dev wp-phpunit/wp-phpunit:6.9 for example, if
your plugin targets a specific WordPress release rather than always the latest. Left
unpinned, Composer resolves the newest available tag, which is usually what you want
in active development but worth being deliberate about once you’re stabilizing a
release.
Step 3: Point bootstrap.php at the Composer-installed library
The scaffolded bootstrap.php looks for the test library via a WP_TESTS_DIR
environment variable, the SVN-checkout convention. Update it to prefer the
Composer-installed path first, falling back to the old convention only if it’s set:
// File: tests/bootstrap.php
<?php
require_once dirname( __DIR__ ) . '/vendor/autoload.php';
// Composer autoloader must load before WP_PHPUNIT__DIR becomes available.
$_tests_dir = getenv( 'WP_TESTS_DIR' ) ?: getenv( 'WP_PHPUNIT__DIR' );
if ( ! $_tests_dir ) {
$_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib';
}
require_once $_tests_dir . '/includes/functions.php';
function _manually_load_plugin() {
require dirname( __DIR__ ) . '/my-plugin.php';
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
require $_tests_dir . '/includes/bootstrap.php';
WP_PHPUNIT__DIR is set automatically once the Composer package is installed and its
autoloader has run, no manual path-finding needed. Your plugin’s own bootstrap file
loads on muplugins_loaded, the same hook point WP-CLI’s own scaffold template uses,
so your abilities are registered on wp_abilities_api_init exactly as they would be on
a real site by the time any test runs.
Step 4: Provide a tests config file
The test library needs real database credentials to create its test tables against. Copy the sample config WordPress core ships and point it at your test database:
# File: terminal
curl -sSL -o tests/wp-tests-config.php \
https://github.com/WordPress/wordpress-develop/raw/trunk/wp-tests-config-sample.php
Edit the copied file’s DB_NAME, DB_USER, DB_PASSWORD, and DB_HOST to match your
local test database, and tell the library where to find it via an environment
variable rather than hardcoding a path:
<!-- File: phpunit.xml.dist -->
<phpunit bootstrap="tests/bootstrap.php">
<php>
<env name="WP_PHPUNIT__TESTS_CONFIG" value="tests/wp-tests-config.php" />
</php>
<testsuites>
<testsuite name="my-plugin">
<directory suffix="Test.php">./tests/Unit/</directory>
<directory suffix="Test.php">./tests/Integration/</directory>
</testsuite>
</testsuites>
</phpunit>
Splitting Unit/ and Integration/ here on purpose, matching the same layout the MCP
Adapter’s own test suite uses, unit tests for pure logic, integration tests for
anything touching real WordPress state, keeps a fast subset you can run constantly
separate from the slower suite that needs the full WordPress bootstrap.
Step 5: Add a Composer script and run it
{
"scripts": {
"test": "phpunit"
}
}
# File: terminal
composer test
Test it: confirm the sample test actually runs against a real WordPress bootstrap
Replace the scaffolded tests/test-sample.php with a one-line sanity check before
writing anything real:
// File: tests/Unit/BootstrapSanityTest.php
<?php
class BootstrapSanityTest extends WP_UnitTestCase {
public function test_wordpress_functions_are_available(): void {
$this->assertTrue( function_exists( 'wp_register_ability' ) );
}
}
Run composer test and confirm it passes. If wp_register_ability doesn’t exist,
your WordPress version in wp-phpunit/wp-phpunit predates the Abilities API’s
inclusion in core, bump the pinned version.
WP_PHPUNIT__TABLE_PREFIX overrides the default wptests_ prefix if you need it, and
the same config file supports multisite testing the same way a normal
wp-tests-config.php always has. Neither is needed for the tests this course builds,
but both exist if your plugin’s own test suite grows past what’s covered here.
Recap
wp scaffold plugin-tests generates the starting file structure, but swap its
SVN-based install-wp-tests.sh convention for wp-phpunit/wp-phpunit plus the
required yoast/phpunit-polyfills companion, both real Composer packages that keep
the WordPress core test library a pinned, reproducible dependency rather than a shell
script your CI pipeline has to run separately. Point bootstrap.php at
WP_PHPUNIT__DIR, provide a wp-tests-config.php with real database credentials, and
composer test gives you a working WP_UnitTestCase base for every lesson that
follows.
Resources & further reading
- wp-phpunit/wp-phpunit documentation, GitHub
- wp-phpunit/wp-phpunit package, Packagist
- How to add automated unit tests to your WordPress plugin, developer.wordpress.org
- wp scaffold plugin-tests, developer.wordpress.org