diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php
index e30fd87..2f7af03 100644
--- a/core/lib/Drupal/Core/Extension/module.api.php
+++ b/core/lib/Drupal/Core/Extension/module.api.php
@@ -823,7 +823,8 @@ function hook_updater_info_alter(&$updaters) {
  *   - value: The current value (e.g., version, time, level, etc). During
  *     install phase, this should only be used for version numbers, do not set
  *     it if not applicable.
- *   - description: The description of the requirement/status.
+ *   - description: The description of the requirement/status. May be either a
+ *     string or a render array.
  *   - severity: The requirement's result/severity level, one of:
  *     - REQUIREMENT_INFO: For info only.
  *     - REQUIREMENT_OK: The requirement is satisfied.
diff --git a/core/modules/system/src/Tests/Module/HookRequirementsTest.php b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
index 83fdce9..eb4a614 100644
--- a/core/modules/system/src/Tests/Module/HookRequirementsTest.php
+++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
@@ -8,24 +8,224 @@
 namespace Drupal\system\Tests\Module;
 
 /**
- * Attempts enabling a module that fails hook_requirements('install').
+ * Tests hook_requirements().
  *
  * @group Module
  */
 class HookRequirementsTest extends ModuleTestBase {
+
+  /**
+   * The requirement phase.
+   *
+   * @var string
+   */
+  protected $phase;
+
+  /**
+   * The requirement severity.
+   *
+   * @var int
+   */
+  protected $severity;
+
+  /**
+   * Whether to use a render array for the requirement description text.
+   *
+   * @var bool
+   */
+  protected $descriptionArray;
+
+  /**
+   * Whether to use a requirement value.
+   *
+   * @var bool
+   */
+  protected $useValue;
+
+  /**
+   * Tests the result hook_requirements() during install and runtime phases.
+   *
+   * @see requirements1_test_requirements()
+   */
+  function testHookRequirements() {
+
+    // Test installing the module with all possible combinations of values
+    // in the hook_requirements() hook.
+
+    // Test all allowed severities.
+    foreach ([REQUIREMENT_ERROR, REQUIREMENT_WARNING, REQUIREMENT_OK, REQUIREMENT_INFO] as $this->severity) {
+      // Test all allowed phases.
+      foreach (['install', 'update', 'runtime'] as $this->phase) {
+        // Test with a render array description and a string description.
+        foreach ([TRUE, FALSE] as $this->descriptionArray) {
+          // Test with and without a 'value' key.
+          foreach ([TRUE, FALSE] as $this->useValue) {
+            $this->doInstallTest();
+          }
+        }
+      }
+    }
+
+    // Now, configure the requirements hook to allow installation, and install
+    // the module.
+    \Drupal::state()->set('requirements1_test.severity', REQUIREMENT_INFO);
+    $this->container->get('module_installer')->install(['requirements1_test']);
+
+    // Test the status report with all possible combinations of values
+    // in the hook_requirements() hook.
+    // Test all allowed severities.
+    foreach ([REQUIREMENT_ERROR, REQUIREMENT_WARNING, REQUIREMENT_OK, REQUIREMENT_INFO] as $this->severity) {
+      // Test all allowed phases.
+      foreach (['install', 'update', 'runtime'] as $this->phase) {
+        // Test with a render array description and a string description.
+        foreach ([TRUE, FALSE] as $this->descriptionArray) {
+          // Test with and without a 'value' key.
+          foreach ([TRUE, FALSE] as $this->useValue) {
+            // Test the runtime messages.
+            $this->doRuntimeTest();
+          }
+        }
+      }
+    }
+
+    // @todo Test update phase separately.
+  }
+
   /**
-   * Assert that a module cannot be installed if it fails hook_requirements().
+   * Tests installing the module with the current hook_requirements() values.
+   *
+   * @see requirements1_test_requirements()
    */
-  function testHookRequirementsFailure() {
-    $this->assertModules(array('requirements1_test'), FALSE);
+  protected function doInstallTest() {
+
+    // Only install phase requirement errors should fail installation.
+    if (($this->phase == 'install') && ($this->severity == REQUIREMENT_ERROR)) {
+      $success = FALSE;
+    }
+    else {
+      $success = TRUE;
+    }
+
+    // Ensure that the module is not currently installed.
+    $this->assertModules(['requirements1_test'], FALSE);
+
+    // Configure the module's hook_requirements().
+    \Drupal::state()->set('requirements1_test.phase', $this->phase);
+    \Drupal::state()->set('requirements1_test.severity', $this->severity);
+    \Drupal::state()->set('requirements1_test.description_array', $this->descriptionArray);
+    \Drupal::state()->set('requirements1_test.use_value', $this->useValue);
 
-    // Attempt to install the requirements1_test module.
-    $edit = array();
+    // Attempt to install the module.
+    $edit = [];
     $edit['modules[Testing][requirements1_test][enable]'] = 'requirements1_test';
     $this->drupalPostForm('admin/modules', $edit, t('Install'));
 
-    // Makes sure the module was NOT installed.
-    $this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
-    $this->assertModules(array('requirements1_test'), FALSE);
+    // Assert the expected success or failure.
+    $this->assertModules(['requirements1_test'], $success);
+
+    // If the installation was successful, no requirements message should be
+    // displayed. A message is only displayed during the install phase when
+    // installation fails. See the hook_requirements() documentation.
+    if ($success) {
+      $this->assertNoRequirementsMessages();
+
+      // Uninstall the module to prepare for the next test.
+      $this->container->get('module_installer')->uninstall(['requirements1_test']);
+    }
+
+    // Otherwise, if the installation was not successful, the requirements
+    // error should be displayed.
+    else {
+      $this->assertExpectedRequirementsMessages();
+    }
+
+  }
+
+  /**
+   * Tests the status report with the current hook_requirements() values.
+   *
+   * @see requirements1_test_requirements()
+   */
+  protected function doRuntimeTest() {
+
+    // Configure the module's hook_requirements().
+    \Drupal::state()->set('requirements1_test.phase', $this->phase);
+    \Drupal::state()->set('requirements1_test.severity', $this->severity);
+    \Drupal::state()->set('requirements1_test.description_array', $this->descriptionArray);
+    \Drupal::state()->set('requirements1_test.use_value', $this->useValue);
+
+    // Visit the reports page.
+    $this->drupalGet('admin/reports/status');
+
+    // If the runtime phase was used, we can expect messages for any severity.
+    if ($this->phase == 'runtime') {
+      $this->assertExpectedRequirementsMessages();
+    }
+
+    // For other phase values, none of the messages should be displayed.
+    else {
+      $this->assertNoRequirementsMessages();
+    }
+  }
+
+  /**
+   * Asserts the expected hook messages with the current seettings.
+   *
+   * @see requirements1_test_requirements()
+   */
+  protected function assertExpectedRequirementsMessages() {
+    // The requirement title should be displayed.
+    $this->assertText('Requirements 1 title');
+
+    // If the requirements description was a render array, check that it was
+    // rendered correctly.
+    if ($this->descriptionArray) {
+      $this->assertText('Requirements 1 render array');
+      $this->assertText('Requirements 1 first item');
+      $this->assertText('Requirements 1 second item');
+    }
+    // Otherwise, test for the string message.
+    else {
+      $this->assertText('Requirements 1 string');
+    }
+
+    // The value should be displayed if it was set.
+    if ($this->useValue) {
+      $this->assertText('Requirements 1 value text');
+    }
+    else {
+      $this->assertNoText('Requirements 1 value text');
+    }
+  }
+
+  /**
+   * Asserts that none of the possible requirements messages are displayed.
+   *
+   * @see requirements1_test_requirements()
+   */
+  protected function assertNoRequirementsMessages() {
+    $this->assertNoText('Requirements 1 title');
+    $this->assertNoText('Requirements 1 string');
+    $this->assertNoText('Requirements 1 render array');
+    $this->assertNoText('Requirements 1 first item');
+    $this->assertNoText('Requirements 1 second item');
+    $this->assertNoText('Requirements 1 value text');
+  }
+
+  /**
+   * {@inheritoc}
+   */
+  protected function assertText($text, $message = '', $group = 'Other') {
+    $message = "Phase {$this->phase}, severity {$this->severity}, value {$this->useValue}: $text found.";
+    parent::assertText($text, $message, $group);
   }
+
+  /**
+   * {@inheritoc}
+   */
+  protected function assertNoText($text, $message = '', $group = 'Other') {
+    $message = "Phase {$this->phase}, severity {$this->severity}, value {$this->useValue}: $text not found.";
+    parent::assertNoText($text, $message, $group);
+  }
+
 }
diff --git a/core/modules/system/src/Tests/Module/ModuleTestBase.php b/core/modules/system/src/Tests/Module/ModuleTestBase.php
index 1f225bb..c2d457f 100644
--- a/core/modules/system/src/Tests/Module/ModuleTestBase.php
+++ b/core/modules/system/src/Tests/Module/ModuleTestBase.php
@@ -30,7 +30,7 @@
   protected function setUp() {
     parent::setUp();
 
-    $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer modules'));
+    $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer modules', 'administer site configuration'));
     $this->drupalLogin($this->adminUser);
   }
 
diff --git a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
index 80220b9..c02dc80 100644
--- a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
+++ b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
@@ -2,18 +2,53 @@
 
 /**
  * Implements hook_requirements().
+ *
+ * Test modules may use four state variables to control this hook:
+ * - requirements1_test.phase: Whether to add the message for 'install', 'update', or 'runtime' phase.
+ * - requirements1_test.severity: The value for the 'severity' of the
+ *   requirement, e.g. REQUIREMENT_ERROR
+ * - requirements1_test.description_array: Boolean indicating whether to use a
+ *   render array for the description message (TRUE) or a plain string (FALSE).
+ * - requirements1_test.use_value: Whether to use the fantastic 'value' key,
+ *   which has different meanings in different contexts!
+ *
+ * @see hook_requirements()
+ * @see \Drupal\system\Tests\Module\HookRequirementsTest
  */
 function requirements1_test_requirements($phase) {
   $requirements = array();
 
-  // Always fails requirements.
-  if ('install' == $phase) {
-    $requirements['requirements1_test'] = array(
-      'title' => t('Requirements 1 Test'),
-      'severity' => REQUIREMENT_ERROR,
-      'description' => t('Requirements 1 Test failed requirements.'),
-    );
+  // Add the requirement only for the phase set in the test.
+  if ($phase != \Drupal::state()->get('requirements1_test.phase')) {
+    return $requirements;
   }
 
+  // Prepare the requirement message.
+  $requirement = [];
+  $requirement['title'] = 'Requirements 1 title';
+
+  // Use the severity level set in the test.
+  $requirement['severity'] = \Drupal::state()->get('requirements1_test.severity');
+
+  // Add a value to the requirement if indicated in the test.
+  if (\Drupal::state()->get('requirements1_test.use_value')) {
+    $requirement['value'] = 'Requirements 1 value text';
+  }
+
+  // Use a render array or a string for the description based on the value set
+  // in the test.
+  if (\Drupal::state()->get('requirements1_test.description_array')) {
+    $requirement['description'] = [
+      '#theme' => 'item_list',
+      '#title' => 'Requirements 1 render array',
+      '#items' => ['Requirements 1 first item', 'Requirements 1 second item'],
+    ];
+  }
+  else {
+    $requirement['description'] = 'Requirements 1 string.';
+  }
+
+  $requirements['requirements1_test'] = $requirement;
   return $requirements;
+
 }
