DrupalUnitTestBase.php 8.57 KB
Newer Older
1 2 3 4 5 6 7 8 9
<?php

/**
 * @file
 * Contains Drupal\simpletest\DrupalUnitTestBase.
 */

namespace Drupal\simpletest;

10
use Drupal\Core\DrupalKernel;
11
use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
12
use Symfony\Component\DependencyInjection\Reference;
13
use Drupal\Core\Database\Database;
14
use Symfony\Component\DependencyInjection\ContainerInterface;
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

/**
 * Base test case class for Drupal unit tests.
 *
 * Tests extending this base class can access files and the database, but the
 * entire environment is initially empty. Drupal runs in a minimal mocked
 * environment, comparable to the one in the installer or update.php.
 *
 * The module/hook system is functional and operates on a fixed module list.
 * Additional modules needed in a test may be loaded and added to the fixed
 * module list.
 *
 * @see DrupalUnitTestBase::$modules
 * @see DrupalUnitTestBase::enableModules()
 */
abstract class DrupalUnitTestBase extends UnitTestBase {

  /**
   * Modules to enable.
   *
   * Test classes extending this class, and any classes in the hierarchy up to
   * this class, may specify individual lists of modules to enable by setting
   * this property. The values of all properties in all classes in the hierarchy
   * are merged.
   *
   * Unlike UnitTestBase::setUp(), any modules specified in the $modules
   * property are automatically loaded and set as the fixed module list.
   *
   * Unlike WebTestBase::setUp(), the specified modules are loaded only, but not
   * automatically installed. Modules need to be installed manually, if needed.
   *
   * @see DrupalUnitTestBase::enableModules()
   * @see DrupalUnitTestBase::setUp()
   *
   * @var array
   */
  public static $modules = array();

  /**
   * Fixed module list being used by this test.
   *
   * @var array
   *   An associative array containing the required data for the $fixed_list
   *   argument of module_list().
   *
   * @see UnitTestBase::setUp()
   * @see UnitTestBase::enableModules()
   */
  private $moduleList = array();

  private $moduleFiles;
  private $themeFiles;
  private $themeData;

69 70 71 72 73 74 75
  /**
   * A KeyValueMemoryFactory instance to use when building the container.
   *
   * @var \Drupal\Core\KeyValueStore\KeyValueMemoryFactory.
   */
  protected $keyValueFactory;

76 77 78 79 80 81 82 83 84 85 86 87 88 89
  /**
   * Sets up Drupal unit test environment.
   *
   * @see DrupalUnitTestBase::$modules
   * @see DrupalUnitTestBase
   */
  protected function setUp() {
    // Copy/prime extension file lists once to avoid filesystem scans.
    if (!isset($this->moduleFiles)) {
      $this->moduleFiles = state()->get('system.module.files') ?: array();
      $this->themeFiles = state()->get('system.theme.files') ?: array();
      $this->themeData = state()->get('system.theme.data') ?: array();
    }

90 91
    $this->keyValueFactory = new KeyValueMemoryFactory();

92
    parent::setUp();
93
    // Build a minimal, partially mocked environment for unit tests.
94 95 96
    $this->containerBuild(drupal_container());
    // Make sure it survives kernel rebuilds.
    $GLOBALS['conf']['container_bundles'][] = 'Drupal\simpletest\TestBundle';
97 98 99 100 101

    state()->set('system.module.files', $this->moduleFiles);
    state()->set('system.theme.files', $this->themeFiles);
    state()->set('system.theme.data', $this->themeData);

102
    // Bootstrap the kernel.
103 104
    // No need to dump it; this test runs in-memory.
    $this->kernel = new DrupalKernel('testing', TRUE, drupal_classloader(), FALSE);
105 106
    $this->kernel->boot();

107 108 109 110 111 112 113 114 115 116 117 118 119 120
    // Ensure that the module list is initially empty.
    $this->moduleList = array();
    // Collect and set a fixed module list.
    $class = get_class($this);
    $modules = array();
    while ($class) {
      if (property_exists($class, 'modules')) {
        $modules = array_merge($modules, $class::$modules);
      }
      $class = get_parent_class($class);
    }
    $this->enableModules($modules, FALSE);
  }

121 122 123 124 125 126 127 128 129 130
  /**
   * Sets up the base service container for this test.
   *
   * Extend this method in your test to register additional service overrides
   * that need to persist a DrupalKernel reboot. This method is only called once
   * for each test.
   *
   * @see DrupalUnitTestBase::setUp()
   * @see DrupalUnitTestBase::enableModules()
   */
131
  public function containerBuild($container) {
132
    global $conf;
133 134
    // Keep the container object around for tests.
    $this->container = $container;
135
    $container->register('lock', 'Drupal\Core\Lock\NullLockBackend');
136
    $conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend');
137
    $container
138 139 140
      ->register('config.storage', 'Drupal\Core\Config\FileStorage')
      ->addArgument($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
    $conf['keyvalue_default'] = 'keyvalue.memory';
141
    $container->set('keyvalue.memory', $this->keyValueFactory);
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
    if (!$container->has('keyvalue')) {
      // TestBase::setUp puts a completely empty container in
      // drupal_container() which is somewhat the mirror of the empty
      // environment being set up. Unit tests need not to waste time with
      // getting a container set up for them. Drupal Unit Tests might just get
      // away with a simple container holding the absolute bare minimum. When
      // a kernel is overridden then there's no need to re-register the keyvalue
      // service but when a test is happy with the superminimal container put
      // together here, it still might a keyvalue storage for anything (for 
      // eg. module_enable) using state() -- that's why a memory service was
      // added in the first place.
      $container
        ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory')
        ->addArgument(new Reference('service_container'));
    }
157 158
  }

159 160 161
  /**
   * Installs a specific table from a module schema definition.
   *
162 163
   * Use this to install a particular table from System module.
   *
164 165 166 167 168 169
   * @param string $module
   *   The name of the module that defines the table's schema.
   * @param string $table
   *   The name of the table to install.
   */
  protected function installSchema($module, $table) {
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
    // drupal_get_schema_unprocessed() is technically able to install a schema
    // of a non-enabled module, but its ability to load the module's .install
    // file depends on many other factors. To prevent differences in test
    // behavior and non-reproducible test failures, we only allow the schema of
    // explicitly loaded/enabled modules to be installed.
    if (!module_exists($module)) {
      throw new \RuntimeException(format_string("'@module' module is not enabled.", array(
        '@module' => $module,
      )));
    }
    $schema = drupal_get_schema_unprocessed($module, $table);
    if (empty($schema)) {
      throw new \RuntimeException(format_string("Unable to retrieve '@module' module schema for '@table' table.", array(
        '@module' => $module,
        '@table' => $table,
      )));
    }
    Database::getConnection()->schema()->createTable($table, $schema);
    // We need to refresh the schema cache, as any call to drupal_get_schema()
    // would not know of/return the schema otherwise.
    // @todo Refactor Schema API to make this obsolete.
    drupal_get_schema(NULL, TRUE);
192 193 194 195 196 197 198 199 200 201 202
  }

  /**
   * Enables modules for this test.
   *
   * Callbacks invoked by module_enable() may need to access information
   * provided by info hooks of the new modules already. However, module_enable()
   * enables the new modules in the system.module configuration only, but that
   * has no effect, since we are operating with a fixed module list.
   *
   * @param array $modules
203 204
   *   A list of modules to enable. Dependencies are not resolved; i.e.,
   *   multiple modules have to be specified with dependent modules first.
205 206 207 208 209 210 211 212 213 214
   * @param bool $install
   *   (optional) Whether to install the list of modules via module_enable().
   *   Defaults to TRUE. If FALSE, the new modules are only added to the fixed
   *   module list and loaded.
   *
   * @todo Remove this method as soon as there is an Extensions service
   *   implementation that is able to manage a fixed module list.
   */
  protected function enableModules(array $modules, $install = TRUE) {
    // Set the modules in the fixed module_list().
215
    $new_enabled = array();
216 217
    foreach ($modules as $module) {
      $this->moduleList[$module]['filename'] = drupal_get_filename('module', $module);
218 219 220 221 222 223 224
      $new_enabled[$module] = dirname($this->moduleList[$module]['filename']);
      module_list(NULL, $this->moduleList);

      // Call module_enable() to enable (install) the new module.
      if ($install) {
        module_enable(array($module), FALSE);
      }
225 226
    }
    // Otherwise, only ensure that the new modules are loaded.
227
    if (!$install) {
228
      module_load_all(FALSE, TRUE);
229
      module_implements_reset();
230 231
    }
  }
232

233
}