wp scaffold plugin-tests writes a working PHPUnit setup for a WordPress plugin in one command. This covers a plain PHPUnit test, what the scaffold generates, the install script, which PHPUnit version the WordPress test suite still needs, and the naming rule that decides whether a test runs at all.
What a test buys us
We state the expected outcome once, in code, instead of clicking the same path by hand after every change. That is the whole pitch, and it is enough.
A test with no WordPress in it
<?php
use PHPUnit\Framework\TestCase;
class MovieLibraryTest extends TestCase {
public function test_totals_add_up() {
$this->assertSame( 5, $this->get_total( 3, 2 ) );
}
private function get_total( $a, $b ) {
return $a + $b;
}
}composer require --dev phpunit/phpunit
./vendor/bin/phpunit testsassertSame() compares value and type, so '5' fails against 5. assertEquals() would let it through. Most of testing is that one line, repeated.
How PHPUnit finds tests
- file -> the name ends in
Test.php, which is PHPUnit’s default suffix along with.phpt - method -> public, and the name starts with
test, or it carries the#[Test]attribute (PHPUnit 10 and newer)
A method called check_totals with neither is not a failing test. It is a test that was never there, and the run still comes back green.
Scaffolding the WordPress version
Run it anywhere inside the WordPress install, with the plugin slug:
wp scaffold plugin-tests my-pluginThe scaffold finds the main file through get_plugins(), so the plugin needs a valid plugin header. Without one it guesses my-plugin.php. It writes:
bin/install-wp-tests.sh-> downloads WordPress and the test suite, and creates the test databasetests/bootstrap.php-> loads the plugin for the test runtests/test-sample.php-> one sample test extendingWP_UnitTestCasephpunit.xml.dist-> the PHPUnit config.phpcs.xml.dist-> a PHP_CodeSniffer ruleset. Running it is a separate job.circleci/config.yml-> CI config.--ci=github,gitlaborbitbucketswaps the provider
The bootstrap does not activate the plugin, whatever the docs say. It requires the main file on muplugins_loaded. Activation hooks only fire from activate_plugin(), so anything in register_activation_hook() has not run when the tests start.
The install script
./bin/install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]db-host defaults to localhost and wp-version to latest. Pass true as the sixth argument to skip creating the database. If a database with that name already exists, the script asks before dropping it, so a real database is safe as long as we read the prompt.
Use a throwaway database name anyway. The test bootstrap drops and reinstalls the WordPress tables on every run, unless WP_TESTS_SKIP_INSTALL=1 is set.
Two things differ from the 2022 notes. First, every mysql and mysqladmin call already passes --user and --password, and has since a February 2020 fix. The three lines those notes said to patch by hand need no patching. Second, since March 2026 the script downloads WordPress and the test suite as tarballs from GitHub instead of SVN, uses the MariaDB client when it finds one, and warns when a newer copy of itself exists. A plugin scaffolded before that keeps the old script, because it is a copy, not a dependency.
Those flags are two hyphens. If a command copied off a WordPress site has ever failed on us, this is why: wptexturize() turns --user into an en dash anywhere outside <code> and <pre>.
Which PHPUnit version?
The WordPress test bootstrap refuses to run without the Yoast PHPUnit Polyfills, version 1.1.0 or newer. Core’s own composer.json requires yoast/phpunit-polyfills ^1.1.0, and the 1.x line supports PHPUnit 4.8 to 9.x. In practice, integration tests for a plugin run on PHPUnit 9.
composer require --dev "phpunit/phpunit:^9" "yoast/phpunit-polyfills:^1.1"PHPUnit itself is on 13.4 as of October 2026. 13 - 9 => 4 major versions, and the #[Test] attribute from earlier is no use here.
The scaffolded phpunit.xml.dist agrees. It still sets convertErrorsToExceptions and its siblings, attributes PHPUnit 10 dropped.
Two naming conventions in one project
Plain PHPUnit looks for files ending Test.php. The scaffolded config looks for something else:
<testsuites>
<testsuite name="testing">
<directory prefix="test-" suffix=".php">./tests/</directory>
<exclude>./tests/test-sample.php</exclude>
</testsuite>
</testsuites>prefix="test-", so inside a scaffolded plugin the files are test-movie-library.php, not MovieLibraryTest.php. Same tool, opposite convention, and the only symptom of getting it wrong is zero tests collected.
Once the install script has run, vendor/bin/phpunit from the plugin root runs the suite.
What this does not cover
Mocking, the factories on WP_UnitTestCase, and wiring any of it into CI.

Leave a comment