Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

Previously, Drupal core provided two base classes for authoring tests; one for unit tests and one for functional/browser/integration web tests:

  1. Drupal\simpletest\UnitTestBase (DrupalUnitTestCase in Drupal 7) sets up essentially nothing. It only prevents the test from changing the environment of the test runner that is executing the test. The test class is on its own, and to unit test a certain function it needs to mock and inject any possibly needed dependencies.

  2. Note:Note: UnitTestBase has been removed in favor of PHPUnit unit tests in Drupal 8.

  3. Drupal\simpletest\WebTestBase installs an entire Drupal site using a specified installation profile through an internal browser. The test class can install further extensions, use the internal browser to access and assert expectations in the test site, and it can invoke all functionality that is available in the Drupal site, as if the site would have been installed regularly.

Even though many tests are only unit-testing functions, they had to use WebTestBase, just because functions being called by the test happen to contain hook invocations or ModuleHandlerInterface::alter(). They have no use for the entire installed Drupal site and also do not need the internal browser. For such tests, Drupal 8 introduces:

  1. Drupal\simpletest\KernelTestBase sets up a completely empty Drupal environment, but with a functional module/hook/extension system and configuration system. It also sets up a database and filesystem, but both are empty.

    KernelTestBase replaces a range of core services with in-memory implementations (e.g., Cache, Lock, KeyValue) so they are functional but do not try to store data persistently (because there is no storage). As in a unit test, the test class is on its own, but it is able to enable modules (loading their code to participate in hooks) and to install modules (including their database schema). This allows to perform API-level integration tests for functionality that cannot be properly injected currently.

The primary reason for introducing KernelTestBase is test performance. A web test requires significant amount of time to install the internal Drupal site (which isn't even used by the test in the end).

When is it appropriate to use KernelTestBase?

All of the following conditions must be true:

  • If your test does not need the internal browser and web access to the Drupal site.
  • If you are testing the expected behavior of a particular class or function only.
  • If you tried hard to use UnitTestBase, and only had to use WebTestBase, because of errors and exceptions being caused by missing module/extension/cache/lock services.
  • If you need to enable or even install a module in order to test a function of it (e.g., to install its configuration or database tables).
  • If the functionality you are testing cannot be properly injected into a unit test.

When is it NOT appropriate to use KernelTestBase?

In case any of the following conditions are met:

  • If you can properly inject all dependencies with mocked services into a unit test.
  • If you need the internal browser to perform requests. (e.g., drupalGet(), drupalPost())
  • If the functionality you are testing could behave differently in a fully installed Drupal environment.

Usage

KernelTestBase shares some aspects with WebTestBase and provides custom methods for enabling modules and installing a database schema:

  • ::$modules allows to specify a list of modules to load. The specified modules are only loaded and "enabled" as a so called "fixed" (locked) module list, but not installed:

      /**
       * Modules to enable.
       *
       * @var array
       */
      public static $modules = array('config_test');
    
  • $this->enableModules() allows to enable additional modules:

      $this->enableModules(array('mymodule_test'));
    

    Important: module_enable() cannot be used directly. Always use the test helper method $this->enableModules().

  • $this->installSchema() allows to install database table schema:

      // Install System module's {queue} database table schema.
      $this->installSchema('system', 'queue');
      // Multiple tables from the same module can be installed at once.
      $this->installSchema('system', array('queue', 'keyvalue'));
    
  • $this->installConfig() allows to install the default configuration of a module:

      // Install default configuration from filter.module.
      $this->installConfig('filter');
    
  • $this->render() allows to render a render array + using content assertion methods:

      $build = array(
        '#markup' => 'Foo',
      );
      $this->render($build);
      $this->assertText('Foo');
    
  • $this->containerBuild() allows to add or override service definitions in the base dependency injection container that is used whenever the DrupalKernel is updated/rebuilt:

    class MyTest extends KernelTestBase {
    
      protected function containerBuild() {
        global $conf;
        parent::containerBuild();
    
        // Replace the keyvalue service with the in-memory implementation.
        $conf['keyvalue_default'] = 'keyvalue.memory';
        $this->container
          ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory');    
      }
    
    }
    

    Note: The above example is set up by KernelTestBase::containerBuild() already; you do not need to duplicate that.

    Note: If your test does not use enableModules(), then you most likely do not need to use containerBuild().

Impacts: 
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done