class ModuleHandlerTest
Tests Drupal\Core\Extension\ModuleHandler.
Attributes
#[CoversClass(ModuleHandler::class)]
#[Group('Extension')]
#[RunTestsInSeparateProcesses]
Hierarchy
- class \Drupal\Tests\UnitTestCase uses \Drupal\Tests\PhpUnitCompatibilityTrait, \Prophecy\PhpUnit\ProphecyTrait, \Drupal\TestTools\Extension\DeprecationBridge\ExpectDeprecationTrait, \Drupal\Tests\RandomGeneratorTrait extends \PHPUnit\Framework\TestCase
- class \Drupal\Tests\Core\Extension\ModuleHandlerTest uses \Drupal\Tests\Core\GroupIncludesTestTrait extends \Drupal\Tests\UnitTestCase
Expanded class hierarchy of ModuleHandlerTest
File
-
core/
tests/ Drupal/ Tests/ Core/ Extension/ ModuleHandlerTest.php, line 26
Namespace
Drupal\Tests\Core\ExtensionView source
class ModuleHandlerTest extends UnitTestCase {
use GroupIncludesTestTrait;
/**
* Get a module handler object to test.
*
* Since we have to run these tests in separate processes, we have to use
* test objects which are serializable. Since ModuleHandler will populate
* itself with Extension objects, and since Extension objects will try to
* access DRUPAL_ROOT when they're unserialized, we can't store our mocked
* ModuleHandler objects as a property in unit tests. They must be generated
* by the test method by calling this method.
*
* @param list<string, string> $modules
* Module paths by module name.
* @param array<string, array<callable-string, string>> $implementations
* Module names by function name implementing hook_hook().
* @param array<string, array<string, string>> $includes
* Include files per hook.
* @param array<string, array<string, string>> $group_includes
* Group include files per hook.
* @param bool $loadAll
* TRUE to call ModuleHandler->loadAll() on the new module handler.
*
* @return \Drupal\Core\Extension\ModuleHandler
* The module handler to test.
*/
protected function getModuleHandler($modules = [], $implementations = [], array $includes = [], array $group_includes = [], $loadAll = TRUE) {
// This only works if there's a single $hook.
$modules['module_handler_test'] = 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test';
$moduleList = [];
foreach ($modules as $module => $path) {
$filename = "{$module}.module";
$moduleList[$module] = [
'type' => 'module',
'pathname' => "{$path}/{$module}.info.yml",
'filename' => file_exists("{$this->root}/{$path}/{$filename}") ? $filename : NULL,
];
}
$keyvalue = new KeyValueMemoryFactory();
$cache = new NullBackend('bootstrap');
$keyvalue->get('hook_data')
->set('hook_list', $implementations);
$keyvalue->get('hook_data')
->set('includes', $includes);
$keyvalue->get('hook_data')
->set('group_includes', $group_includes);
$callableResolver = $this->createMock(CallableResolver::class);
$callableResolver->expects($this->any())
->method('getCallableFromDefinition')
->willReturnCallback(fn($definition) => $definition);
$moduleHandler = new ModuleHandler($this->root, $moduleList, $keyvalue, $callableResolver, $cache);
if ($loadAll) {
$moduleHandler->loadAll();
}
return $moduleHandler;
}
/**
* Tests loading a module.
*
* @legacy-covers ::load
*/
public function testLoadModule() : void {
$moduleList = [
'module_handler_test_added' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added',
];
$module_handler = $this->getModuleHandler($moduleList);
$this->assertTrue($module_handler->load('module_handler_test'));
$this->assertTrue(function_exists('module_handler_test_hook'));
$this->assertTrue($module_handler->load('module_handler_test_added'));
$this->assertTrue(function_exists('module_handler_test_added_helper'), 'Function exists after being loaded.');
$this->assertTrue($module_handler->load('module_handler_test_added'));
$this->assertFalse($module_handler->load('module_handler_test_dne'), 'Non-existent modules returns false.');
}
/**
* Tests loading all modules.
*
* @legacy-covers ::loadAll
*/
public function testLoadAllModules() : void {
$moduleList = [
'module_handler_test_all1' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1',
'module_handler_test_all2' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2',
];
$module_handler = $this->getModuleHandler($moduleList);
$module_handler->loadAll();
$this->assertTrue(function_exists('module_handler_test_all1_hook'), 'Function exists after being loaded.');
$this->assertTrue(function_exists('module_handler_test_all2_hook'), 'Function exists after being loaded.');
}
/**
* Tests reload method.
*
* @legacy-covers ::reload
*/
public function testModuleReloading() : void {
$module_handler = $this->getMockBuilder(ModuleHandler::class)
->setConstructorArgs([
$this->root,
[
'module_handler_test' => [
'type' => 'module',
'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
'filename' => 'module_handler_test.module',
],
],
new KeyValueMemoryFactory(),
$this->createMock(CallableResolver::class),
new NullBackend('bootstrap'),
])
->onlyMethods([
'load',
])
->getMock();
$calls = [
'module_handler_test',
];
$module_handler->expects($this->once())
->method('load')
->with($this->callback(function (string $module) use (&$calls) : bool {
return $module === array_shift($calls);
}));
$module_handler->reload();
}
/**
* Tests isLoaded accessor.
*
* @legacy-covers ::isLoaded
*/
public function testIsLoaded() : void {
$module_handler = $this->getModuleHandler(loadAll: FALSE);
$this->assertFalse($module_handler->isLoaded());
$module_handler->loadAll();
$this->assertTrue($module_handler->isLoaded());
}
/**
* Confirm we get back the modules set in the constructor.
*
* @legacy-covers ::getModuleList
*/
public function testGetModuleList() : void {
$this->assertEquals($this->getModuleHandler()
->getModuleList(), [
'module_handler_test' => new Extension($this->root, 'module', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml', 'module_handler_test.module'),
]);
}
/**
* Confirm we get back a module from the module list.
*
* @legacy-covers ::getModule
*/
public function testGetModuleWithExistingModule() : void {
$this->assertEquals($this->getModuleHandler()
->getModule('module_handler_test'), new Extension($this->root, 'module', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml', 'module_handler_test.module'));
}
/**
* Tests get module with non existing module.
*
* @legacy-covers ::getModule
*/
public function testGetModuleWithNonExistingModule() : void {
$this->expectException(UnknownExtensionException::class);
$this->getModuleHandler()
->getModule('claire_alice_watch_my_little_pony_module_that_does_not_exist');
}
/**
* Ensure setting the module list replaces the module list and resets internal structures.
*
* @legacy-covers ::setModuleList
*/
public function testSetModuleList() : void {
$fixture_module_handler = $this->getModuleHandler();
$module_handler = $this->getMockBuilder(ModuleHandler::class)
->setConstructorArgs([
$this->root,
[],
new KeyValueMemoryFactory(),
$this->createMock(CallableResolver::class),
new NullBackend('bootstrap'),
])
->onlyMethods([
'resetImplementations',
])
->getMock();
// Ensure we reset implementations when settings a new modules list.
$module_handler->expects($this->once())
->method('resetImplementations');
// Make sure we're starting empty.
$this->assertEquals([], $module_handler->getModuleList());
// Replace the list with a prebuilt list.
$module_handler->setModuleList($fixture_module_handler->getModuleList());
// Ensure those changes are stored.
$this->assertEquals($fixture_module_handler->getModuleList(), $module_handler->getModuleList());
}
/**
* Tests module exists returns correct module status.
*
* @legacy-covers ::moduleExists
*/
public function testModuleExists() : void {
$module_handler = $this->getModuleHandler();
$this->assertTrue($module_handler->moduleExists('module_handler_test'));
$this->assertFalse($module_handler->moduleExists('module_handler_test_added'));
}
/**
* Tests load all includes.
*
* @legacy-covers ::loadAllIncludes
*/
public function testLoadAllIncludes() : void {
$this->expectDeprecation('ModuleHandler::loadAllIncludes() is deprecated in drupal:11.3.0 and is removed from drupal:13.0.0. There is no replacement. See https://www.drupal.org/node/3536432');
$this->assertTrue(TRUE);
$module_handler = $this->getMockBuilder(ModuleHandler::class)
->setConstructorArgs([
$this->root,
[
'module_handler_test' => [
'type' => 'module',
'pathname' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test/module_handler_test.info.yml',
'filename' => 'module_handler_test.module',
],
],
new KeyValueMemoryFactory(),
$this->createMock(CallableResolver::class),
new NullBackend('bootstrap'),
])
->onlyMethods([
'loadInclude',
])
->getMock();
// Ensure we reset implementations when settings a new modules list.
$module_handler->expects($this->once())
->method('loadInclude');
$module_handler->loadAllIncludes('hook');
}
/**
* Tests loadInclude().
*
* Note we load code, so isolate the test.
*
* @legacy-covers ::loadInclude
*/
public function testLoadInclude() : void {
$module_handler = $this->getModuleHandler();
// Include exists.
$this->assertEquals(__DIR__ . '/modules/module_handler_test/hook_include.inc', $module_handler->loadInclude('module_handler_test', 'inc', 'hook_include'));
$this->assertTrue(function_exists('module_handler_test_hook_include'));
// Include doesn't exist.
$this->assertFalse($module_handler->loadInclude('module_handler_test', 'install'));
}
/**
* Tests invoke methods when module is enabled.
*
* @legacy-covers ::invoke
*/
public function testInvokeModuleEnabled() : void {
$module_handler = $this->getModuleHandler();
$module_handler->loadAll();
$this->assertTrue($module_handler->invoke('module_handler_test', 'hook', [
TRUE,
]), 'Installed module runs hook.');
$this->assertFalse($module_handler->invoke('module_handler_test', 'hook', [
FALSE,
]), 'Installed module runs hook.');
$this->assertNull($module_handler->invoke('module_handler_test_fake', 'hook', [
FALSE,
]), 'Installed module runs hook.');
}
/**
* Tests implementations methods when module is enabled.
*
* @legacy-covers ::hasImplementations
*/
public function testImplementsHookModuleEnabled() : void {
$implementations = [
'hook' => [
'module_handler_test_hook' => 'module_handler_test',
'module_handler_test_added_hook' => 'module_handler_test_added',
],
];
$moduleList = [
'module_handler_test_added' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added',
'module_handler_test_no_hook' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_no_hook',
];
$includes_per_function = [
'hook' => [
'module_handler_test_added_hook' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added/module_handler_test_added.hook.inc',
],
];
$module_handler = $this->getModuleHandler($moduleList, $implementations, $includes_per_function);
$this->assertTrue($module_handler->hasImplementations('hook', 'module_handler_test'), 'Installed module implementation found.');
$this->assertTrue($module_handler->hasImplementations('hook', 'module_handler_test_added'), 'Runtime added module with implementation in include found.');
$this->assertFalse($module_handler->hasImplementations('hook', 'module_handler_test_no_hook'), 'Missing implementation not found.');
}
/**
* Tests invoke all.
*
* @legacy-covers ::invokeAll
*/
public function testInvokeAll() : void {
$implementations = [
'hook' => [
'module_handler_test_hook' => 'module_handler_test',
'module_handler_test_all1_hook' => 'module_handler_test_all1',
'module_handler_test_all2_hook' => 'module_handler_test_all2',
],
];
$moduleList = [
'module_handler_test_all1' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1',
'module_handler_test_all2' => 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2',
];
$module_handler = $this->getModuleHandler($moduleList, $implementations);
$this->assertEquals([
TRUE,
TRUE,
TRUE,
], $module_handler->invokeAll('hook', [
TRUE,
]));
}
/**
* Tests hasImplementations.
*
* @legacy-covers ::hasImplementations
*/
public function testHasImplementations() : void {
$implementations = [
'some_hook' => [
TestHookClass::class . '::someMethod' => 'some_module',
],
// Set up a hook list closure with empty result.
// This can theoretically happen if the implementations are for modules that
// are not installed.
'empty_hook' => [],
];
$module_handler = $this->getModuleHandler([], $implementations);
$module_handler->setModuleList([
'some_module' => TRUE,
]);
$r = new \ReflectionObject($module_handler);
$get_lists = fn() => $r->getProperty('hookImplementationLists')
->getValue($module_handler);
$this->assertSame([], $get_lists());
$this->assertTrue($module_handler->hasImplementations('some_hook'));
$this->assertEquals([
'some_hook' => new ImplementationList([
TestHookClass::class . '::someMethod',
], [
'some_module',
]),
], $get_lists());
$this->assertFalse($module_handler->hasImplementations('unknown_hook'));
$this->assertEquals([
'some_hook' => new ImplementationList([
TestHookClass::class . '::someMethod',
], [
'some_module',
]),
'unknown_hook' => new ImplementationList([], []),
], $get_lists());
$this->assertFalse($module_handler->hasImplementations('empty_hook'));
$this->assertEquals([
'some_hook' => new ImplementationList([
TestHookClass::class . '::someMethod',
], [
'some_module',
]),
'unknown_hook' => new ImplementationList([], []),
'empty_hook' => new ImplementationList([], []),
], $get_lists());
}
/**
* Tests get module directories.
*
* @legacy-covers ::getModuleDirectories
*/
public function testGetModuleDirectories() : void {
$moduleList = [
'node' => 'core/modules/node',
];
$module_handler = $this->getModuleHandler($moduleList);
$moduleDirectories = [
'node' => $this->root . '/core/modules/node',
'module_handler_test' => $this->root . '/core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test',
];
$this->assertEquals($moduleDirectories, $module_handler->getModuleDirectories());
}
/**
* Tests group includes.
*/
public function testGroupIncludes() : void {
self::setupGroupIncludes();
$this->expectDeprecation('Autoloading hooks in the file (vfs://drupal_root/test_module.tokens.inc) is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Move the functions in this file to either the .module file or other appropriate location. See https://www.drupal.org/node/3489765');
$moduleHandler = $this->getModuleHandler([], [
'token_info' => [
'test_module_token_info' => 'test_module',
],
], [], [
'token_info' => self::GROUP_INCLUDES['token_info'],
]);
$this->assertFalse(function_exists('_test_module_helper'));
$moduleHandler->invokeAll('token_info');
$this->assertTrue(function_exists('_test_module_helper'));
}
}
Members
| Title Sort descending | Modifiers | Object type | Summary | Overrides |
|---|---|---|---|---|
| ExpectDeprecationTrait::expectDeprecation | public | function | Adds an expected deprecation. | |
| ExpectDeprecationTrait::setUpErrorHandler | public | function | Sets up the test error handler. | |
| ExpectDeprecationTrait::tearDownErrorHandler | public | function | Tears down the test error handler. | |
| GroupIncludesTestTrait::GROUP_INCLUDES | constant | |||
| GroupIncludesTestTrait::setupGroupIncludes | public static | function | ||
| ModuleHandlerTest::getModuleHandler | protected | function | Get a module handler object to test. | |
| ModuleHandlerTest::testGetModuleDirectories | public | function | Tests get module directories. | |
| ModuleHandlerTest::testGetModuleList | public | function | Confirm we get back the modules set in the constructor. | |
| ModuleHandlerTest::testGetModuleWithExistingModule | public | function | Confirm we get back a module from the module list. | |
| ModuleHandlerTest::testGetModuleWithNonExistingModule | public | function | Tests get module with non existing module. | |
| ModuleHandlerTest::testGroupIncludes | public | function | Tests group includes. | |
| ModuleHandlerTest::testHasImplementations | public | function | Tests hasImplementations. | |
| ModuleHandlerTest::testImplementsHookModuleEnabled | public | function | Tests implementations methods when module is enabled. | |
| ModuleHandlerTest::testInvokeAll | public | function | Tests invoke all. | |
| ModuleHandlerTest::testInvokeModuleEnabled | public | function | Tests invoke methods when module is enabled. | |
| ModuleHandlerTest::testIsLoaded | public | function | Tests isLoaded accessor. | |
| ModuleHandlerTest::testLoadAllIncludes | public | function | Tests load all includes. | |
| ModuleHandlerTest::testLoadAllModules | public | function | Tests loading all modules. | |
| ModuleHandlerTest::testLoadInclude | public | function | Tests loadInclude(). | |
| ModuleHandlerTest::testLoadModule | public | function | Tests loading a module. | |
| ModuleHandlerTest::testModuleExists | public | function | Tests module exists returns correct module status. | |
| ModuleHandlerTest::testModuleReloading | public | function | Tests reload method. | |
| ModuleHandlerTest::testSetModuleList | public | function | Ensure setting the module list replaces the module list and resets internal structures. | |
| RandomGeneratorTrait::getRandomGenerator | protected | function | Gets the random generator for the utility methods. | |
| RandomGeneratorTrait::randomMachineName | protected | function | Generates a unique random string containing letters and numbers. | |
| RandomGeneratorTrait::randomObject | public | function | Generates a random PHP object. | |
| RandomGeneratorTrait::randomString | public | function | Generates a pseudo-random string of ASCII characters of codes 32 to 126. | |
| UnitTestCase::$root | protected | property | The app root. | |
| UnitTestCase::getClassResolverStub | protected | function | Returns a stub class resolver. | |
| UnitTestCase::getConfigFactoryStub | public | function | Returns a stub config factory that behaves according to the passed array. | |
| UnitTestCase::getContainerWithCacheTagsInvalidator | protected | function | Sets up a container with a cache tags invalidator. | |
| UnitTestCase::getStringTranslationStub | public | function | Returns a stub translation manager that just returns the passed string. | |
| UnitTestCase::setDebugDumpHandler | public static | function | Registers the dumper CLI handler when the DebugDump extension is enabled. | |
| UnitTestCase::setUp | protected | function | 366 | |
| UnitTestCase::setupMockIterator | protected | function | Set up a traversable class mock to return specific items when iterated. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.