diff --git a/core/core.services.yml b/core/core.services.yml
index c818c8a..b4949cd 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -42,6 +42,14 @@ parameters:
     maxAge: false
     supportsCredentials: false
 services:
+  # Composer integration.
+  composer.extension_dependency_checker:
+    class: Drupal\Core\Composer\ExtensionDependencyChecker
+    arguments: ['@app.root']
+  composer.extension_dependency_requirements:
+    class: Drupal\Core\Composer\ExtensionDependencyRequirements
+    arguments: ['@composer.extension_dependency_checker', '@string_translation']
+
   # Simple cache contexts, directly derived from the request context.
   cache_context.ip:
     class: Drupal\Core\Cache\Context\IpCacheContext
@@ -486,7 +494,7 @@ services:
     class: Drupal\Core\Extension\ModuleInstaller
     tags:
       - { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator }
-    arguments: ['@app.root', '@module_handler', '@kernel', '@router.builder']
+    arguments: ['@app.root', '@module_handler', '@kernel', '@composer.extension_dependency_checker']
     lazy: true
   content_uninstall_validator:
     class: Drupal\Core\Entity\ContentUninstallValidator
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 3a9c2bc..48c1a3a 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\OpCodeCache;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\Core\Site\Settings;
 
@@ -985,9 +986,20 @@ function drupal_requirements_severity(&$requirements) {
  */
 function drupal_check_module($module) {
   module_load_install($module);
-  // Check requirements
-  $requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', array('install'));
-  if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
+  // Check requirements based on hook_requirements(). Make sure the requirements
+  // are an array, even if the hook invocation returns NULL if the module does
+  // not implement hook_requirements().
+  $requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', array('install')) ?: [];
+
+  // We can't use the module handler here because it only gives us enabled
+  // extensions.
+  $extension = new Extension(DRUPAL_ROOT, 'module', drupal_get_filename('module', $module));
+  // Add Composer dependencies to requirements.
+  /** @var \Drupal\Core\Composer\ExtensionDependencyRequirements $dependency_requirements */
+  $dependency_requirements = \Drupal::service('composer.extension_dependency_requirements');
+  $requirements = array_merge($requirements, $dependency_requirements->buildRequirements([$extension]));
+
+  if (drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
     // Print any error messages
     foreach ($requirements as $requirement) {
       if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
diff --git a/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
new file mode 100644
index 0000000..4830447
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Drupal\Core\Composer;
+
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\InfoParser;
+
+/**
+ * Checks whether an extension's Composer dependencies are met.
+ */
+class ExtensionDependencyChecker {
+
+  /**
+   * The path to the application's root directory.
+   *
+   * @var string
+   */
+  protected $appRoot;
+
+  /**
+   * The Composer packages that are currently installed.
+   *
+   * @var null|string[]
+   *   Either NULL when the installed packages have not been computed yet, or an
+   *   array of which keys are package names and values are package versions.
+   *   Example: ['crell/api-problem' => '1.7.0'].
+   *
+   * @see self::getInstalledPackages()
+   */
+  protected $installedPackages;
+
+  /**
+   * Creates a new instance.
+   *
+   * @param string $app_root
+   *   The path to the application's root directory.
+   */
+  public function __construct($app_root) {
+    $this->appRoot = $app_root;
+  }
+
+  /**
+   * Tells whether the extension is marked as requiring Composer installation.
+   *
+   * @param \Drupal\Core\Extension\Extension $extension
+   *   The extension to check.
+   *
+   * @return bool
+   *   TRUE if this extension requires Composer installation.
+   */
+  public function requiresComposerInstallation(Extension $extension) {
+    $info_parser = new InfoParser();
+    $info = $info_parser->parse($this->appRoot . '/' . $extension->getPathname());
+    if (isset($info['build_dependencies'])) {
+      if ($info['build_dependencies'] === 'composer') {
+        return TRUE;
+      }
+      if (is_array($info['build_dependencies'])) {
+        return in_array('composer', $info['build_dependencies']);
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Determines if an extension was installed through Composer.
+   *
+   * @param Extension $extension
+   *   The extension being checked.
+   *
+   * @return bool
+   *   TRUE if the extension was installed as a Composer package.
+   */
+  public function installedThroughComposer(Extension $extension) {
+    $installed = $this->getInstalledPackages();
+    return isset($installed['drupal/' . $extension->getName()]);
+  }
+
+  /**
+   * Gets information about installed Composer packages.
+   *
+   * @return string[]
+   *   Keys are package names and values are package versions. Example:
+   *   ['crell/api-problem' => '1.7.0'].
+   */
+  protected function getInstalledPackages() {
+    // Quickly return the cached list, if it exists.
+    if (is_array($this->installedPackages)) {
+      return $this->installedPackages;
+    }
+
+    $installed_json_path = $this->appRoot . '/vendor/composer/installed.json';
+
+    // If installed.json does not exist, no packages are installed.
+    if (!file_exists($installed_json_path)) {
+      $this->installedPackages = [];
+      return $this->installedPackages;
+    }
+
+    // Parse the list once and then cache it.
+    $json = file_get_contents($installed_json_path, FALSE);
+    $this->installedPackages = [];
+    foreach (json_decode($json) as $package) {
+      if (isset($package->name) && isset($package->version_normalized)) {
+        $this->installedPackages[$package->name] = $package->version_normalized;
+      }
+    }
+    return $this->installedPackages;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
new file mode 100644
index 0000000..22098cd
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace Drupal\Core\Composer;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Builds hook_requirements() info about extensions' Composer dependencies.
+ */
+class ExtensionDependencyRequirements {
+
+  use StringTranslationTrait;
+
+  /**
+   * The extension Composer dependency checker.
+   *
+   * @var \Drupal\Core\Composer\ExtensionDependencyChecker
+   */
+  protected $dependencyChecker;
+
+  /**
+   * Creates a new instance.
+   *
+   * @param \Drupal\Core\Composer\ExtensionDependencyChecker $dependency_checker
+   *   The extension Composer dependency checker.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translator.
+   */
+  public function __construct(ExtensionDependencyChecker $dependency_checker, TranslationInterface $string_translation) {
+    $this->dependencyChecker = $dependency_checker;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * Builds the Composer requirements for extensions.
+   *
+   * @param \Drupal\Core\Extension\Extension[] $extensions
+   *   The extensions to build the requirements for.
+   *
+   * @return array[]
+   *   Requirements array, suitable for hook_requirements().
+   *
+   * @see hook_requirements()
+   */
+  public function buildRequirements(array $extensions) {
+    $requirements = [
+      'composer_dependencies' => [
+        'title' => $this->t('Extension Composer dependencies'),
+      ],
+    ];
+
+    $uninstalled_extensions = [];
+    foreach ($extensions as $extension) {
+      if ($this->dependencyChecker->requiresComposerInstallation($extension)) {
+        if (!$this->dependencyChecker->installedThroughComposer($extension)) {
+          $uninstalled_extensions[] = $extension->getName();
+        }
+      }
+    }
+
+    if (empty($uninstalled_extensions)) {
+      $requirements['composer_dependencies'] += [
+        'description' => $this->t('All Composer dependencies have been met.'),
+        'severity' => REQUIREMENT_OK,
+      ];
+    }
+    else {
+      $requirements['composer_dependencies'] += [
+        'description' => $this->t('The following extensions must be installed using Composer: @extensions. Read the <a href=":documentation">documentation on drupal.org</a> on how to install them.', [
+          '@extensions' => implode(', ', $uninstalled_extensions),
+          ':documentation' => 'https://www.drupal.org/documentation/install/composer-dependencies',
+        ]),
+        'severity' => REQUIREMENT_ERROR,
+      ];
+    }
+
+    return $requirements;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
new file mode 100644
index 0000000..d632052
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Exception thrown when an extension requires Composer installation.
+ */
+class ExtensionComposerRequirementsException extends \Exception {
+
+  /**
+   * The machine name of the extension with unmet dependencies.
+   *
+   * @var string
+   */
+  protected $extensionName;
+
+  /**
+   * Constructs a new instance.
+   *
+   * @param string $extension_name
+   *   The machine name of the extension with unmet dependencies.
+   */
+  public function __construct($extension_name) {
+    $this->extensionName = $extension_name;
+    // Construct a message string which can be used without a translation
+    // service.
+    parent::__construct("Extension $extension_name must be installed through Composer.");
+  }
+
+  /**
+   * Gets a translated message from the exception.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation service.
+   *
+   * @return string
+   *   Translated string.
+   */
+  public function getTranslatedMessage(TranslationInterface $string_translation) {
+    return $string_translation->translate("Extension @extension must be installed through Composer.", ['@extension' => $this->extensionName]);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index a903b8a..e849311 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -5,6 +5,7 @@
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Composer\ExtensionDependencyChecker;
 use Drupal\Core\DrupalKernelInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\FieldableEntityInterface;
@@ -18,6 +19,13 @@
 class ModuleInstaller implements ModuleInstallerInterface {
 
   /**
+   * The extension Composer dependency checker.
+   *
+   * @var \Drupal\Core\Composer\ExtensionDependencyChecker
+   */
+  protected $extensionComposerDependencyChecker;
+
+  /**
    * The module handler.
    *
    * @var \Drupal\Core\Extension\ModuleHandlerInterface
@@ -54,11 +62,14 @@ class ModuleInstaller implements ModuleInstallerInterface {
    *   The module handler.
    * @param \Drupal\Core\DrupalKernelInterface $kernel
    *   The drupal kernel.
+   * @param \Drupal\Core\Composer\ExtensionDependencyChecker $extension_composer_dependency_checker
+   *   The extension Composer dependency checker.
    *
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel) {
+  public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel, ExtensionDependencyChecker $extension_composer_dependency_checker) {
+    $this->extensionComposerDependencyChecker = $extension_composer_dependency_checker;
     $this->root = $root;
     $this->moduleHandler = $module_handler;
     $this->kernel = $kernel;
@@ -93,9 +104,15 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
       }
 
       // Add dependencies to the list. The new modules will be processed as
-      // the while loop continues.
+      // the while loop continues. We will check for Composer-based dependencies
+      // as we go.
       while (list($module) = each($module_list)) {
-        foreach (array_keys($module_data[$module]->requires) as $dependency) {
+        $module_extension = $module_data[$module];
+        // Throw an exception if there are unmet Composer-based dependencies.
+        if ($this->extensionComposerDependencyChecker->requiresComposerInstallation($module_extension)) {
+          throw new ExtensionComposerRequirementsException($module);
+        }
+        foreach (array_keys($module_extension->requires) as $dependency) {
           if (!isset($module_data[$dependency])) {
             // The dependency does not exist.
             throw new MissingDependencyException("Unable to install modules: module '$module' is missing its dependency module $dependency.");
@@ -128,6 +145,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
       $source_storage = $config_installer->getSourceStorage();
     }
     $modules_installed = array();
+
     foreach ($module_list as $module) {
       $enabled = $extension_config->get("module.$module") !== NULL;
       if (!$enabled) {
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
index c8221a1..ab714bb 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
@@ -37,6 +37,9 @@
    * @throws \Drupal\Core\Extension\MissingDependencyException
    *   Thrown when a requested module, or a dependency of one, can not be found.
    *
+   * @throws \Drupal\Core\Extension\ExtensionComposerRequirementsException
+   *   Thrown when Composer-based dependencies are not met.
+   *
    * @see hook_module_preinstall()
    * @see hook_install()
    * @see hook_modules_installed()
diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index 7831c38..1903fba 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Config\UnmetDependenciesException;
 use Drupal\Core\Access\AccessManagerInterface;
 use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ExtensionComposerRequirementsException;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Extension\ModuleInstallerInterface;
 use Drupal\Core\Form\FormBase;
@@ -476,6 +477,13 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
         );
         return;
       }
+      catch (ExtensionComposerRequirementsException $e) {
+        drupal_set_message(
+          $e->getTranslatedMessage($this->getStringTranslation()),
+          'error'
+        );
+        return;
+      }
     }
   }
 
diff --git a/core/modules/system/src/Tests/Module/HookRequirementsTest.php b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
index ccbbff9..1f86b21 100644
--- a/core/modules/system/src/Tests/Module/HookRequirementsTest.php
+++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
@@ -2,10 +2,18 @@
 
 namespace Drupal\system\Tests\Module;
 
+use Drupal\Core\Extension\ExtensionComposerRequirementsException;
+
 /**
  * Attempts enabling a module that fails hook_requirements('install').
  *
  * @group Module
+ *
+ * @see \Drupal\Tests\Core\Composer\ExtensionDependencyCheckerTest
+ * @see \Drupal\Tests\Core\Composer\ExtensionDependencyRequirementsTest
+ *
+ * @todo Figure out how to demonstrate installation success for
+ *   Composer-installed modules.
  */
 class HookRequirementsTest extends ModuleTestBase {
   /**
@@ -24,4 +32,53 @@ function testHookRequirementsFailure() {
     $this->assertModules(array('requirements1_test'), FALSE);
   }
 
+  /**
+   * Tests that modules which require Composer installation are un-installable.
+   */
+  public function testComposerModuleFail() {
+    $modules = [
+      [
+        'composer_required_array',
+        'The following extensions must be installed using Composer: composer_required_array. Read the documentation on drupal.org on how to install them.',
+      ],
+      [
+        'composer_required_string',
+        'The following extensions must be installed using Composer: composer_required_string. Read the documentation on drupal.org on how to install them.',
+      ],
+    ];
+    foreach ($modules as $module) {
+      $this->assertComposerModuleUninstallable($module[0], $module[1]);
+    }
+  }
+
+  /**
+   * Tests that a Composer-only module can't be installed.
+   */
+  public function assertComposerModuleUninstallable($module, $error_message) {
+    // Demonstrate that the module is not installed.
+    $this->assertModules([$module], FALSE);
+
+    // Attempt to install the module using internals. This should throw a
+    // \Drupal\Core\Extension\ExtensionComposerRequirementsException.
+    $message = sprintf('Attempting to install %s threw %s.', $module, ExtensionComposerRequirementsException::class);
+    try {
+      $installer = $this->container->get('module_installer');
+      $installer->install([$module], TRUE);
+      $this->fail($message);
+    }
+    catch (ExtensionComposerRequirementsException $e) {
+      $this->pass($message);
+    }
+
+    // Attempt to install the module using the module list form. This should
+    // fail with a message to the user.
+    $edit = [];
+    $edit["modules[Testing][$module][enable]"] = $module;
+    $this->drupalPostForm('admin/modules', $edit, t('Install'));
+
+    $this->assertText($error_message);
+    // Makes sure the module was NOT installed.
+    $this->assertModules([$module], FALSE);
+  }
+
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index b3a0ec1..983f956 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -240,6 +240,11 @@ function system_requirements($phase) {
     $requirements['php_extensions']['value'] = t('Enabled');
   }
 
+  // Check Composer dependencies during runtime, because drupal_check_module()
+  // does it when installing modules already.
+  $dependency_requirements = \Drupal::service('composer.extension_dependency_requirements');
+  $requirements = array_merge($requirements, $dependency_requirements->buildRequirements(\Drupal::moduleHandler()->getModuleList()));
+
   if ($phase == 'install' || $phase == 'runtime') {
     // Check to see if OPcache is installed.
     $opcache_enabled = (function_exists('opcache_get_status') && opcache_get_status()['opcache_enabled']);
diff --git a/core/modules/system/tests/modules/composer_required_array/composer_required_array.info.yml b/core/modules/system/tests/modules/composer_required_array/composer_required_array.info.yml
new file mode 100644
index 0000000..605afdc
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_required_array/composer_required_array.info.yml
@@ -0,0 +1,8 @@
+name: 'Composer Required To Install Array Specification'
+type: module
+description: 'Test module that must be installed using Composer'
+package: Testing
+version: VERSION
+core: 8.x
+build_dependencies:
+  - composer
diff --git a/core/modules/system/tests/modules/composer_required_string/composer_required_string.info.yml b/core/modules/system/tests/modules/composer_required_string/composer_required_string.info.yml
new file mode 100644
index 0000000..8a2d299
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_required_string/composer_required_string.info.yml
@@ -0,0 +1,7 @@
+name: 'Composer Required To Install String Specification'
+type: module
+description: 'Test module that must be installed using Composer'
+package: Testing
+version: VERSION
+core: 8.x
+build_dependencies: composer
diff --git a/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
new file mode 100644
index 0000000..f9726d8
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace Drupal\Tests\Core\Composer;
+
+use Drupal\Core\Composer\ExtensionDependencyChecker;
+use Drupal\Core\Extension\Extension;
+use Drupal\Tests\UnitTestCase;
+use org\bovigo\vfs\vfsStream;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Composer\ExtensionDependencyChecker
+ *
+ * @group Composer
+ */
+class ExtensionDependencyCheckerTest extends UnitTestCase {
+
+  /**
+   * Numeric counter to help with unique file names.
+   *
+   * @var numeric
+   */
+  protected static $counter = 0;
+
+  /**
+   * Provides data to self::testDependenciesAreMet().
+   *
+   * @return array[]
+   *   Every item is an array with the following items:
+   *   - A boolean TRUE if dependencies are expected to be met, FALSE otherwise.
+   *   - Contents of module's composer.json file, or NULL if there is no file.
+   *   - Contents of project's installed.json file, or NULL if there is no file.
+   *   - Array of module dependencies, as if parsed from the info.yml file.
+   */
+  public function providerRequiresComposer() {
+    return [
+      'no composer' => [
+        FALSE,
+        NULL,
+      ],
+      'composer required' => [
+        TRUE,
+        'composer',
+      ],
+      'composer required as array' => [
+        TRUE,
+        ['composer'],
+      ],
+      'composer required among others' => [
+        TRUE,
+        ['composer', 'bower'],
+      ],
+      'build_dependencies but not composer' => [
+        FALSE,
+        ['bower', 'super_build_tool'],
+      ],
+    ];
+  }
+
+  /**
+   * @covers ::requiresComposerInstallation
+   *
+   * @dataProvider providerRequiresComposer
+   */
+  public function testRequiresComposerInstallation($expected, $build_dependencies) {
+    // Since InfoParser keeps a static cache of parsed info keyed by file name,
+    // we have to manage this counter in order to have unique file names.
+    static::$counter++;
+    $counter = static::$counter;
+    $module_name = "some_module$counter";
+
+    // Make an info.yml file.
+    $info = [
+      'type' => 'module',
+      'core' => '8.x',
+      'name' => $module_name,
+    ];
+    if ($build_dependencies !== NULL) {
+      $info['build_dependencies'] = $build_dependencies;
+    }
+    // Make a file system.
+    $structure = [
+      'modules' => [$module_name => ["$module_name.info.yml" => Yaml::dump($info)]],
+    ];
+    vfsStream::setup('root', NULL, $structure);
+
+    $extension = new Extension(vfsStream::url('root'), 'module', "modules/$module_name/$module_name.info.yml");
+    $sut = new ExtensionDependencyChecker(vfsStream::url('root'));
+
+    $this->assertEquals($expected, $sut->requiresComposerInstallation($extension));
+  }
+
+  /**
+   * Determine that the installed.json schema hasn't changed.
+   *
+   * This test exists to fail when or if Composer changes the schema or location
+   * of installed.json.
+   *
+   * @covers ::getInstalledPackages
+   */
+  public function testGetInstalledPackages() {
+    // We need an app root for ExtensionDependencyChecker.
+    $app_root = __DIR__ . '/../../../../../..';
+
+    // Verify that installed.json will be found by getInstalledPackages().
+    $installed_json_path = $app_root . '/vendor/composer/installed.json';
+    $this->assertTrue(file_exists($installed_json_path));
+
+    // Create an ExtensionDependencyChecker to test.
+    $checker = new ExtensionDependencyChecker($app_root);
+
+    // Set getInstalledPackages() to be accessible.
+    $ref_get_installed = new \ReflectionMethod($checker, 'getInstalledPackages');
+    $ref_get_installed->setAccessible(TRUE);
+
+    // Call getInstalledPackages().
+    $installed = $ref_get_installed->invoke($checker);
+    // Verify that we got an array back. If this test fails, then we have a good
+    // indication that Composer has changed it's installed.json file format
+    // and/or location.
+    $this->assertInternalType('array', $installed);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
new file mode 100644
index 0000000..d29df22
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Drupal\Tests\Core\Composer;
+
+use Drupal\Core\Composer\ExtensionDependencyChecker;
+use Drupal\Core\Composer\ExtensionDependencyRequirements;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Composer\ExtensionDependencyRequirements
+ *
+ * @group Composer
+ */
+class ExtensionDependencyRequirementsTest extends KernelTestBase {
+
+  /**
+   * Provides data to self::testBuildRequirements().
+   *
+   * @return array[]
+   *   Every item is an array with the following items:
+   *   - One of the REQUIREMENT_* constants.
+   *   - An unmet dependency with a package name as key and version constraint
+   *     as value.
+   */
+  public function providerBuildRequirements() {
+    // We cannot use any of the REQUIREMENT_* constants here, because providers
+    // are run before environments are booted.
+    return [
+      // REQUIREMENT_ERROR is 2.
+      'REQUIREMENT_ERROR' => [
+        2,
+        TRUE,
+        FALSE,
+      ],
+      // REQUIREMENT_OK is 0.
+      'REQUIREMENT_OK' => [
+        0,
+        TRUE,
+        TRUE,
+      ],
+      'REQUIREMENT_OK' => [
+        0,
+        FALSE,
+        TRUE,
+      ],
+    ];
+  }
+
+  /**
+   * @covers ::buildRequirements
+   *
+   * @dataProvider providerBuildRequirements
+   *
+   * @param int $expected_severity
+   *   One of the REQUIREMENT_* constants.
+   * @param string[] $unmet_dependencies
+   *   An array of unmet dependences, with the package name as key and version
+   *   constraint as value.
+   */
+  public function testBuildRequirements($expected_severity, $needs_composer, $installed_through_composer) {
+    $extension = $this->prophesize(Extension::class);
+
+    $dependency_checker = $this->prophesize(ExtensionDependencyChecker::class);
+    $dependency_checker->requiresComposerInstallation($extension->reveal())->willReturn($needs_composer);
+    $dependency_checker->installedThroughComposer($extension->reveal())->willReturn($installed_through_composer);
+
+    $string_translation = $this->getMock(TranslationInterface::class);
+
+    $sut = new ExtensionDependencyRequirements($dependency_checker->reveal(), $string_translation);
+
+    $requirements = $sut->buildRequirements([$extension->reveal()]);
+    $this->assertCount(1, $requirements);
+    $this->assertArrayHasKey('composer_dependencies', $requirements);
+    $this->assertArrayHasKey('severity', $requirements['composer_dependencies']);
+    $this->assertSame($expected_severity, $requirements['composer_dependencies']['severity']);
+  }
+
+}
