Runtime Assertions

Last updated on
3 May 2022

A runtime assertion is a statement that is expected to always be true at the point in the code it appears at. They are tested using PHP's internal assert() statement. If an assertion is ever FALSE it indicates an error in the code or in module or theme configuration files. User-provided configuration files should be verified with standard control structures at all times, not just checked in development environments with assert() statements on.

Since unit tests also use the term "assertion" to refer to test conditions, the term "runtime assertion" will be used when disambiguation is necessary.

What Assertions are and are not

Assertions are used to test the expectations of the programmer at run time regarding the code state. They openly state conditions that should be impossible when code and configuration are in an error free state. This context sets them apart from exceptions. When you read an assert() statement you are reading something the writer of that assert() believed impossible. If it's not then the code that follows will act in some buggy manner or fail outright, further the code that came before has to have a bug. By stopping the code flow at the assertion point the code following the assertion can be ruled out as the actual source of the problem even though that would be the point of failure were the assert() statement not there.

Another consideration is testing assertions can introduce considerable overhead. For example, cache tags must be strings to be valid and if they are not the cache will become corrupted. Cache tags are only introduced when module developers are creating new code - after tests are concluded and the module deployed such tests become redundant, and they can involve traversing arrays with thousands of elements with an "is_string" statement.

Expectations: Design by Contract

Assert() statements help to define the expectations of the codebase. Drupal has a large API with many public methods that your modules may call, and many that in accordance with the API guidelines your code is expected to call. These methods often expect to receive data from your module in a particular way. PHP 5 solves some of this problem with type hints - enforcing that a module argument is an object or array. But what if it should be a string? What if it should be one of a few specific keywords - such as "template", "module", or so on? What if an array is called for where all the members of the array must fit a certain constraint such as all of them being strings or numbers?

PHP documentation code in the comments only records what the code should be doing, it can't verify that it's actually being done. Only an assert() statement can do that cleanly. It could be checked for with normal control structures, but the performance loss of checking some of Drupal's larger data structures such as render arrays every single time they are passed between public functions would be enormous.

Actively checking expectations this way is a programming paradigm known as "Design by Contract." Think of the API description as a contract - you provide the Drupal code with data in a known range and format and you will receive the same. This isn't a replacement for Test Driven Design but a supplement to it. Unit tests check known scenarios and configurations, and collections of such tests constantly recheck such scenarios to prevent regressions in the system. Functional tests check to see if the whole system behaves as expected, again in known scenarios. At the time of this writing, there are nearly 100,000 such tests in Drupal 8. But what about the unknown?

This is the role of the Design by Contract approach. The tests were done with the assert() statement aren't so much about the components of Drupal themselves but the interactions between them and their interactions with module code written by anyone.

Assert() statements most frequently show up at the start and the end of functions. Those assert() statements at the start are known as "preconditions." They also show up just before the return statement of the code - these are known as post conditions.

Occasionally assert() statements are placed as the default of a switch case statement. Consider the following example dealing with card suits.

switch($suit) {
  case 'heart':
    //some action
    break;
  case 'diamond':
    // some action
    break;
  case 'club':
    // some action
    break;
  case 'spade':
    // some action
    break;
  default:
    assert(FALSE, "Invalid suit {$suit}");
}

Here the assertion only fails when the function this branching appears in gets passed a suit that doesn't exist.

Assertions in Drupal

Assertions are only now being added to Drupal - at this time they are still large areas of the code that aren't being tested this way. The first assertions that will be added to Drupal will deal with the areas of the code module and theme developers work with most frequently, and working out from there.

An assertion inspector component has been provided to do several common generic tests. They are invoked like so.

// First mark that you may use the library after setting the namespace.
use Drupal\Component\Assertion\Inspector;

// Then later in the same PHP file.
assert(Inspector::assertAllStrings($array));

This will assert that all the elements of the $array are strings. See the api documentation for more information on the Inspector's functions.

Configuration Checking

Drupal has several objects that the services.yml file of modules can change. These objects may contain assertions about their own state to make sure they haven't been misconfigured, such as \Drupal\Template\TwigExtension.

  public function getPath($name, $parameters = array(), $options = array()) {
    assert($this->urlGenerator instanceof UrlGeneratorInterface, 'Generator missing. The most likely culprit is a misconfigured services.yml file in a module.');
    $options['absolute'] = FALSE;
    return $this->urlGenerator->generateFromRoute($name, $parameters, $options);
  }

Note: Whether to assert the validity of configuration files is in a gray area between assertions and exceptions. Drupal uses assert when only developers are expected to make changes to the files in question. Configuration files that are modified by admins are to be guarded by exception testing at all times.

Using Assert

You are encouraged to use unit tests whenever possible to test your own code and modules. The assert statement is most useful for modules that provide API's that will be used by other modules. As in Drupal core, assert can be used in those modules to ensure the calling code is meeting the expectations of the API by providing variables of the correct data type. 

The assert statement takes two arguments. The first is the assertion, which is the condition to be tested. The second is either an error message string or an exception to throw.

If you write a unit test to check an assert() statement you must check to see if assertions are on and if they are not skip the test:

if (ini_get('zend.assertions') < 0) {
  $this->markTestSkipped('Assertions disabled, skipping');
}

Doing this preserves the option to all unit tests as a group regardless of whether runtime assertions are turned on. Note - the above also skips the test on PHP 5.

Configuring Your Servers

There are several configuration directives that affect assertions:

  • zend.assertions: set to -1 on production servers and 1 on development servers. If you use automated test runners it is best to run tests once with each setting before sending to production. PHP ships with zend.assertions set to 1, and most service providers change this to -1.
  • assert.exception: should always be set to 1 (has no effect if zend.assertions is disabled)
  • assert.active: should always be set to 1 (has no effect if zend.assertions is disabled). PHP defaults to 1, but overridden by the Drupal default .htaccess file so you may need to re-enable it in settings.local.php.

Help improve this page

Page status: No known problems

You can: