Application Testing with PHPUnit – WordPress Plugin Boilerplate
Demo Plugin runs PHPUnit in the isolated .wp-env.test.json environment. npm run test:php starts it automatically.
The container provides a real WordPress database and the full WordPress function library required by the WordPress test bootstrap. The bootstrap lives in tests/bootstrap.php, configuration in phpunit.xml.dist, and test classes go in tests/php.
Write your first test
Section titled “Write your first test”-
Add a test class in
tests/php. AnExampleTest.phpis already provided as a starting point.tests/php/MyFeatureTest.php <?phpclass MyFeatureTest extends WP_UnitTestCase {public function test_something(): void {$this->assertTrue( true );}} -
Run tests.
Terminal npm run test:php
Common test patterns
Section titled “Common test patterns”WordPress data with factories
Section titled “WordPress data with factories”<?php
class PostMetaBehaviorTest extends WP_UnitTestCase { public function test_can_store_and_read_post_meta(): void { $post_id = self::factory()->post->create(); update_post_meta( $post_id, '_demo_flag', 'yes' );
$this->assertSame( 'yes', get_post_meta( $post_id, '_demo_flag', true ) ); }}Hooks and filters
Section titled “Hooks and filters”<?php
class FilterRegistrationTest extends WP_UnitTestCase { public function test_demo_filter_modifies_value(): void { add_filter( 'demo_plugin/example_value', static fn( string $value ): string => $value . '-modified' );
$result = apply_filters( 'demo_plugin/example_value', 'before' ); $this->assertSame( 'before-modified', $result ); }}Plugin dependencies for testing
Section titled “Plugin dependencies for testing”Some tests require other plugins to be present and active in the test environment.
Free plugins from WordPress.org
Section titled “Free plugins from WordPress.org”Use WP-CLI inside the isolated test environment. The plugin slug matches its wordpress.org/plugins slug:
npm run env:test:cli -- wp plugin install advanced-custom-fields --version=6.3.0 --activatePremium or private plugins
Section titled “Premium or private plugins”Private and premium plugins are managed through a dedicated tests/composer.json. Composer installs them into tests/setup/plugins/, which is mapped into the wp-env WordPress installation before the environment starts.
Run npm run env:test:cli -- composer update --working-dir=tests to update test dependencies.
-
Add the private Composer repository and package to
tests/composer.json. Use an explicit installer path keyed to the package name so the install directory is predictable.tests/composer.json {"repositories": [{"type": "composer","url": "https://composer.my-premium-plugin.com"}],"require": {"vendor/my-premium-plugin": "*"},"config": {"allow-plugins": {"composer/installers": true}}} -
Create
tests/auth.jsonlocally with credentials for the private repository.tests/auth.json {"http-basic": {"composer.my-premium-plugin.com": {"username": "your-license-email","password": "your-license-key"}}} -
Run the tests.
Terminal npm run test:php -
For CI, add a
COMPOSER_AUTHsecret in GitHub Actions with the same JSON content asauth.json. Thetest-applicationworkflow writes it totests/auth.jsonbefore the tests run.GitHub Actions secret: COMPOSER_AUTH {"http-basic": {"composer.my-premium-plugin.com": {"username": "your-license-email","password": "your-license-key"}}}
Host-side tests
Section titled “Host-side tests”Future REST or browser tests can use getWpEnvUrls() from tests/helpers/wp-env.mjs to resolve the URLs reported by WordPress.
Configuration notes
Section titled “Configuration notes”Namespaces in test files — WP_UnitTestCase is a global class so test classes conventionally use no namespace. If you prefer a namespace, add a Tests\ autoload entry in composer.json.
Local phpunit.xml — phpunit.xml.dist is committed as the shared default. Create a local phpunit.xml to override settings for your environment (it is already listed in .gitignore).
The test-application job in .github/workflows/test-analyse.yml runs the full test suite on every push via wp-env.