Aryan Jasala

PHPUnit tests for a WordPress plugin, scaffold to first assertion

A test flask beside a green tick

wp scaffold plugin-tests generates a working PHPUnit setup for a WordPress plugin in one command, and then the install script falls over on MySQL credentials. This covers the PHPUnit basics, what the scaffold writes, the three lines that need fixing, and the naming rule that quietly decides whether your tests run at all.

Where the scaffold needs editing
wp scaffold plugin-testswrites config, bootstrap, install script
install-wp-tests.shfails on MySQL credentials
Patch three callsadd –user and –password
phpunitruns from the plugin root

Two hyphens, not one dash: wptexturize rewrites commands copied off a page.

The scaffold writes a working setup; only the install script needs patching, in three places, when local MySQL wants a user and password.

What a test buys you

You state the expected outcome once, in code, instead of clicking the same path by hand after every change. That is the entire pitch and it is enough. ✅

A test with no WordPress in it

Extend PHPUnitFrameworkTestCase:

<?php
use PHPUnitFrameworkTestCase;

class MovieLibraryTest extends TestCase {

    public function test_totals_add_up() {
        $expected = 5;
        $result   = $this->get_total( 3, 2 );
        $this->assertSame( $expected, $result );
    }

    public function get_total( $a, $b ) {
        return $a + $b;
    }
}

Run everything in the directory:

./vendor/bin/phpunit tests

assertSame compares expected against actual and decides the outcome. Most of testing is that line, repeated. ⚖️

The two naming rules

  • a file is a test file if the name ends in Test.php -> MovieLibraryTest.php
  • a method is a test if the name starts with test, or if it carries a /** @test */ annotation

Both method forms work:

/** @test */
public function totals_add_up() {}

public function test_totals_add_up() {}

Pick one and stay with it. A method called check_totals with no annotation is not a failing test, it is a test that was never there, and the run still comes back green. 🙃

Test.php suffix versus test- prefix

Plain PHPUnit

  • Discovery by suffix Test.php
  • MovieLibraryTest.php
  • PHPUnit default, no config needed

Scaffolded plugin

  • Discovery by prefix test-
  • test-movie-library.php
  • Set in phpunit.xml.dist testsuite

Same tool, opposite convention. The only symptom of getting it wrong is zero tests collected.

A scaffolded plugin discovers test files by the test- prefix, not the Test.php suffix.

Scaffolding the WordPress version

From inside the plugin directory, which needs a valid plugin header before any of this works:

wp scaffold plugin-tests <plugin-name>

That writes more than the name suggests:

  • bin/install-wp-tests.sh -> sets up the test suite and the test database
  • tests/bootstrap.php -> activates the plugin for the test run
  • tests/test-sample.php -> one working test
  • phpunit.xml.dist -> the PHPUnit config
  • .phpcs.xml.dist -> a PHP_CodeSniffer ruleset, thrown in free. Running it is a separate job
  • .circleci/config.yml -> CI config, and --ci changes which provider you get

Then build the test database:

./bin/install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host] [wp-version]

db-host and wp-version are optional. The database you name gets dropped and recreated, so do not point it at one you want to keep. 🗑️

The MySQL flags the script assumes

If your local MySQL wants credentials, three calls in install-wp-tests.sh need them passed explicitly. In recreate_db:

mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA

In create_db:

mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA

And in install_db:

mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep ^$DB_NAME

Those are two hyphens, not one dash. If you have ever pasted a command off a WordPress site and watched it fail, this is why: wptexturize turns --user into an en dash before anyone reads it. 🤡

Two naming conventions in one project

Here is the part that catches people. Plain PHPUnit looks for files ending Test.php. The scaffolded phpunit.xml.dist looks for something else:

<testsuites>
    <testsuite>
        <directory prefix="test-" suffix=".php">./tests/</directory>
        <exclude>./tests/test-sample.php</exclude>
    </testsuite>
</testsuites>

prefix="test-", so inside a scaffolded WordPress 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, 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.

The flag edits above are for a local MySQL that expects a user and password. A socket-authenticated setup will not need them, so try the script unmodified first. 🔌