I will provide a dirty workaround here, that can be easily copied without adjusting any code.
There might be better solutions, I am open for other answers!
Also, I won't promise 100% that this works in all circumstances. Perhaps opcache can bring nasty surprises?
Create the following file in "tests/src/$modulename/Traits/AutoloadHelperTrait.php"
.
Replace <MODULENAME>
with the real module name.
<?php
/**
* @file
* Registers the entire 'tests/src/' directory in the class loader.
*
* By default, Drupal phpunit integration registers only _some_ namespace
* directories within 'tests/src/' in the class loader. One of them is
* '/Traits/'.
* This workaround registers the top-level namespace.
*
* The code is written in a way that can easily be copied to other modules:
* - It is independent of the module name.
* - It is independent of where the module is placed in the filesystem.
*
* See https://drupal.stackexchange.com/questions/309143/autoload-helper-classes-in-tests-src-for-phpunit-in-contrib-module
*
* @see \drupal_phpunit_populate_class_loader()
* @see \drupal_phpunit_get_extension_namespaces()
*/
declare(strict_types=1);
namespace Drupal\Tests\<MODULENAME>\Traits;
use Composer\Autoload\ClassLoader;
\call_user_func(static function (): void {
// The Composer ClassLoader class is always present in the same location
// within the vendor directory. From there we can work our way to the
// autoload.php to get the real class loader.
$rc = new \ReflectionClass(ClassLoader::class);
/** @var \Composer\Autoload\ClassLoader $classLoader */
$classLoader = require dirname($rc->getFileName(), 2) . '/autoload.php';
// Determine the namespace and directory from magic constants.
// This allows the code to be copied elsewhere.
$level = \substr_count(__NAMESPACE__, '\\');
$dir = dirname(__DIR__, $level - 2);
// The namespace always starts with 'Drupal\Tests\', independent of the
// module name.
$nspos = \strpos(__NAMESPACE__, '\\', 13);
$namespace = \substr(__NAMESPACE__, 0, $nspos + 1);
$classLoader->addPsr4($namespace, $dir);
});
/**
* Include this trait to register the entire tests/src/ for class loading.
*/
trait AutoloadHelperTrait {}
Now include the trait in any test class that needs this autoloading enabled. Or include it from a test base class.