Skip to content

[PHPUnit Bridge] Fails with Class "PHPUnit\TextUI\Application" not found in .../.phpunit/phpunit-11.5-0/phpunit #60482

Closed
@Philipp91

Description

@Philipp91

Symfony version(s) affected

7.2

Description

FYI I'm filing this just to document the problem and the solution I found. If you're a maintainer, no need to read it all, no need to respond.

I just upgraded from PHPUnit 9.5 to 11.5 (by changing the SYMFONY_PHPUNIT_VERSION) and now I get a new error:

PHP Fatal error:  Uncaught Error: Class "PHPUnit\TextUI\Application" not found in .../vendor/bin/.phpunit/phpunit-11.5-0/phpunit:104
Stack trace:
#0 .../vendor/symfony/phpunit-bridge/bin/simple-phpunit.php(473): include()
#1 .../vendor/symfony/phpunit-bridge/bin/simple-phpunit(13): require('...')
#2 .../vendor/bin/simple-phpunit(119): include('...')
#3 {main}
thrown in .../vendor/bin/.phpunit/phpunit-11.5-0/phpunit on line 104

How to reproduce

  • <server name="SYMFONY_PHPUNIT_VERSION" value="11.5"/> (or 12.1, doesn't matter) in phpunit.xml.dist.
  • Not have phpunit/phpunit as a (dev) dependency in the outer composer.json.
  • Run vendor/bin/simple-phpunit

Possible Solution

I believe it has to do with this change by @xabbuh. It removed the file_put_contents('phpunit', ...) line (and surrounding lines) if PHPUnit >= 11. This line replaced the PHPUnit main script with the contents between EOPHP there. The main purpose main purpose of using that patched script was to use Symfony\Bridge\PhpUnit\TextUI\Command::main(); instead of the (new PHPUnit\TextUI\Application)->run($_SERVER['argv']) from the standard PHPUnit main script. The documented reason was to inject logic for Symfone's test listener feature, which had since been removed, hence it was no longer necessary to inject that alternative main script either.

However, there's another behavioral difference:

The old/patched script always loaded this:

define('PHPUNIT_COMPOSER_INSTALL', __DIR__.'/vendor/autoload.php');
require PHPUNIT_COMPOSER_INSTALL;

whereas since PHPUnit 8, the official script does this:

if (isset($GLOBALS['_composer_autoload_path'])) {
    define('PHPUNIT_COMPOSER_INSTALL', $GLOBALS['_composer_autoload_path']);

    unset($GLOBALS['_composer_autoload_path']);
} else {
    foreach (array(__DIR__ . '/../../autoload.php', __DIR__ . '/../vendor/autoload.php', __DIR__ . '/vendor/autoload.php') as $file) {
        if (file_exists($file)) {
            define('PHPUNIT_COMPOSER_INSTALL', $file);

            break;
        }
    }

    unset($file);
}

require PHPUNIT_COMPOSER_INSTALL;

That is: If an outer Composer autoload is detected, that is used instead of the inner one. But the inner one is where phpunit-bridge intalls the phpunit/phpunit package.


In the past, it wasn't necessary to have phpunit/phpunit as a dependency on the outer composer.json, but now it is. That's fine, as some Symfony documentation asks to install symfony/test-pack (which brings in phpunit/phpunit) and some other Symfony documentation asks to install phpunit/phpunit itself. Doing so fixes the problem.

Maybe this should be documented as a breaking change for Symfony 7.2.

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions