Score:1

Autoload helper classes in tests/src/ for phpunit in contrib module

in flag

I am working on a contrib module with phpunit tests.

Currently there is no composer.json or phpunit.xml or installation routine in the module itself, instead one has to run the functional and browser tests from a custom drupal site, where the module is installed in web/modules/contrib/*. So far this works.

Now I want to use helper classes in tests/src/Helper/, in namespace "Drupal\\Tests\\$modulename\\Helper\\" and possibly elsewhere.

However, I noticed that drupal_phpunit_get_extension_namespaces() only registers some select namespaces within "Drupal\\Tests\\$modulename\\", instead of the entire namespace.

My questions:

  • Why does Drupal phpunit integration behave this way, instead of registering the entire tests/src/ namespace directory?
  • What is a good way to register the entire tests/src/ directory in the class loader? I still want to be able to run the tests from Drupal installation outside the module itself.

EDIT: New issue on drupal.org: #3258817: Why don't we register all of /tests/src/ for class loading?

Score:0
in flag

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.

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.