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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions