yoast/wp-test-utils
Yoast WP Test Utils provides PHPUnit test case classes and helpers for WordPress plugins and themes, making it easier to write reliable unit and integration tests with proper WordPress bootstrapping and mocking.
yoast/wp-test-utils is not a Laravel package — it is a thin, opinionated compatibility layer that sits between your test file and either (a) the WordPress core test suite or (b) Brain Monkey. For a Laravel developer the realistic entry points are narrow:
get_option, wp_remote_get, apply_filters) and want it green in CI without booting WordPress.Install it as a dev dependency and keep it in its own test suite:
composer require --dev yoast/wp-test-utils
composer require --dev brain/monkey # only if you want the unit-test flavour
Where to look first, in order:
src/BrainMonkey/TestCase.php — the base class for unit tests. It wires up Brain\Monkey\setUp()/tearDown() for you.src/WPIntegration/TestCase.php — the replacement for WP_UnitTestCase that smooths over set_up() vs setUp() and PHPUnit 5→10 differences.src/Helpers/ExpectOutputHelper.php — expectOutputString()/expectOutputRegex() for PHPUnit versions where output expectations were removed.src/WPIntegration/bootstrap-functions.php — path resolution helpers for locating the WP test library.The fastest first win is converting one brittle unit test. Take a class that calls get_option() and apply_filters(), extend Yoast\WPTestUtils\BrainMonkey\TestCase, and stub those globals with Brain Monkey. No database, no WordPress bootstrap, milliseconds per test. Do that before touching integration tests — the integration path requires a real MySQL database and the WP core test suite checked out somewhere.
Realistic expectation-setting: 62 stars and zero Packagist dependents means you are on a lightly-trodden path. Yoast's own plugin repos consume it, but you will not find a large Stack Overflow corpus. Budget time to read src/ — it is small enough to read end-to-end in twenty minutes, which is the best documentation you will get.
Unit tests with Brain Monkey. The base class handles lifecycle, so your test only contains expectations:
use Yoast\WPTestUtils\BrainMonkey\TestCase;
use Brain\Monkey\Functions;
final class OptionsTest extends TestCase {
protected function set_up(): void {
parent::set_up(); // snake_case, not setUp()
Functions\when('is_admin')->justReturn(false);
Functions\expect('get_option')
->once()
->with('my_plugin_settings', [])
->andReturn(['enabled' => true]);
}
public function test_reads_settings(): void {
self::assertTrue((new Settings())->enabled());
}
}
This is the direct analogue of Laravel's Http::fake() / Facade::shouldReceive() but for global functions — which is the only mocking surface WP code gives you.
Integration tests. Extend Yoast\WPTestUtils\WPIntegration\TestCase instead of WP_UnitTestCase. You get the WP factories (self::factory()->post->create()), go_to(), set_current_user(), and transactional rollback per test. The value over vanilla WP_UnitTestCase is that the same test file works across WP 5.x → 6.x and PHPUnit 7 → 10 without conditional method_exists( $this, 'set_up' ) shims in your own code.
Bootstrap layout. Put a tests/phpunit/bootstrap.php that requires Composer's autoloader, then loads the WP test library (includes/functions.php, includes/bootstrap.php) using the path helpers the package provides. Do not try to reuse Laravel's bootstrap/app.php here — see gotchas.
Pest. You can register the base class per-directory:
uses(Yoast\WPTestUtils\BrainMonkey\TestCase::class)->in('tests/Unit/WordPress');
CI matrix. The whole point of the package is cross-version safety, so pin a GitHub Actions matrix on PHP × WP version × PHPUnit version. Use yoast/phpunit-polyfills (a transitive dependency) assertions — assertIsArray, assertObjectHasProperty — and avoid raw PHPUnit APIs that were renamed between majors. If you are testing a plugin, download the matching wordpress-develop tag per matrix cell rather than testing against trunk only.
Where it composes with a Laravel codebase. Keep the WP suite as a separate Composer autoload namespace and a separate phpunit-wp.xml. Shared domain classes can then be tested twice: once under orchestra/testbench against Laravel facades, once under Brain Monkey against WP globals, with the same assertions. That dual harness is the main reason a Laravel developer would reach for this package at all.
set_up() vs setUp() is the number one footgun. Both base classes expect you to override the snake_case WP naming. If you override setUp() instead, you silently skip Brain Monkey bootstrapping or the polyfill bridge, and you get "function already defined" or "undefined function" errors that point nowhere useful. Always call parent::set_up().ABSPATH, WP_DEBUG, and loads wp-settings.php. It must run before any of your WP-facing classes are autoloaded, and it cannot coexist in the same PHP process as a booted Laravel application. Run them as separate CI jobs or separate phpunit invocations — never a single suite with both.wp-includes/plugin.php is already loaded, Brain Monkey cannot redefine those functions. Use BrainMonkey\TestCase for pure unit tests and WPIntegration\TestCase for anything that needs real WP behaviour. There is no useful middle ground.TestCaseNoPolyfills exists for a reason. If your project (or another dev dependency) already loads yoast/phpunit-polyfills, using the standard integration TestCase produces duplicate function declarations. Switch to Yoast\WPTestUtils\WPIntegration\TestCaseNoPolyfills and load the polyfills once yourself.WP_TESTS_DOMAIN, and the WP test library on disk. Most cryptic failures ("Cannot find WordPress installation") are just an unset WP_TESTS_DIR/WP_DEVELOP_DIR or a path helper resolving relative to the wrong directory. Add a var_dump() of the resolved path in bootstrap once; it will save an hour later.files autoload. In a large monorepo that means they are always in scope — check for function-name collisions if you also ship your own src/functions.php helpers.@dataProvider is deprecated/removed in newer PHPUnit; the package follows polyfill conventions, so migrate providers to #[DataProvider('...')] attributes if your matrix includes PHPUnit 10, or keep the matrix capped at 9.set_up()/tear_down(), add helper assertions in a project-level abstract TestCase, or use Yoast\WPTestUtils\BrainMonkey\YoastTestCase as a starting point if you want the Yoast-flavoured expectation helpers. There is no service container, no config file, no plugin system — which is a feature: reading src/ tells you everything.composer update that bumps it can change which polyfill shims are loaded and flip previously-green tests. Lock it in composer.json alongside your PHPUnit constraint.How can I help you explore Laravel packages today?