Skip to content

Make src/{Unit,Integration}/bootstrap.php self-locating to drop per-project init-tests.php #52

Description

@remyperona

🤖 AI-generated — created with Claude Code, reviewed by @Tabrisrp. Surfaced while migrating wp-media/mcp-oauth to wp-media/phpunit v3.2. Contributes to #30.

Problem

Every consumer plugin must ship duplicate bootstrap shims — Tests/Unit/init-tests.php and Tests/Integration/init-tests.php — that each define two constants and delegate to the library bootstrap. This per-plugin boilerplate runs counter to the #30 goal of reducing maintenance across plugins.

Why the shim is currently required

The library bootstraps via src/Unit/bootstrap.php and src/Integration/bootstrap.php. These files use the constants WPMEDIA_PHPUNIT_ROOT_DIR and WPMEDIA_PHPUNIT_ROOT_TEST_DIR but do not define them — they assume the caller defined them first.

  • When run through the wpmedia-phpunit binary, BootstrapManager::setupConstants() defines these constants in the parent process.
  • However, for @runInSeparateProcess tests, PHPUnit re-runs the phpunit-config bootstrap file standalone in a child process where the binary never runs.
  • If the phpunit config's bootstrap= points directly at src/Unit/bootstrap.php, the child fatals with Undefined constant "WPMEDIA_PHPUNIT_ROOT_DIR".
  • Consumers therefore keep an init-tests.php shim purely to define those constants (guarded) before requiring the library bootstrap, so it works in both the binary (parent) and isolated-child contexts.

A typical consumer shim (from mcp-oauth):

if ( ! defined( 'WPMEDIA_PHPUNIT_ROOT_DIR' ) ) {
    define( 'WPMEDIA_PHPUNIT_ROOT_DIR', dirname( dirname( __DIR__ ) ) );
}
if ( ! defined( 'WPMEDIA_PHPUNIT_ROOT_TEST_DIR' ) ) {
    define( 'WPMEDIA_PHPUNIT_ROOT_TEST_DIR', __DIR__ );
}
require_once WPMEDIA_PHPUNIT_ROOT_DIR . '/vendor/wp-media/phpunit/src/Unit/bootstrap.php';

Proposed solution

Make src/Unit/bootstrap.php and src/Integration/bootstrap.php self-sufficient by defining the constants when absent, reusing existing logic:

if ( ! defined( 'WPMEDIA_PHPUNIT_ROOT_DIR' ) ) {
    \WPMedia\PHPUnit\BootstrapManager::setupConstants( 'unit' ); // 'integration' in the Integration bootstrap
}

BootstrapManager::setupConstants() already derives the project root from __DIR__ and defaults the test dir to Tests/ + ucfirst(suite), so no consumer input is required.

Result: a consumer points their phpunit config at bootstrap="…/vendor/wp-media/phpunit/src/Unit/bootstrap.php" (or relies on the bundled config) and deletes both init-tests.php files. Isolated children work because the bootstrap now self-derives the constants when re-run standalone.

Secondary: phpunit.xml.dist duplication

BootstrapManager::getPhpunitXml() already falls back to the bundled src/{Unit,Integration}/phpunit.xml.dist when a consumer has none — good. But the bundled config can't express a consumer's project-specific code-coverage source dir (inc/ vs src/ vs includes/), and PHPUnit 9 has no native config inheritance, so consumers keep a full phpunit.xml.dist mainly to scope <coverage>.

Suggestion: let the bundled config's <coverage><include> read the source directory from an env var (e.g. WPMEDIA_PHPUNIT_SRC) or composer extra, so conventional-layout projects need no phpunit.xml.dist at all. (Config inheritance itself is a PHPUnit limitation, not something the library can fully solve.)

Impact

Combined with the self-locating bootstrap, a consumer's per-suite footprint shrinks to just its genuinely project-specific Tests/{Unit,Integration}/bootstrap.php (which wires the plugin under test) — directly advancing the #30 goal of reducing per-plugin maintenance.

References

Contributes to #30.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions