diff --git a/core/core.services.yml b/core/core.services.yml
index afccb31..7c2a17f 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -34,6 +34,14 @@ parameters:
     - webcal
     - rtsp
 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
@@ -468,7 +476,7 @@ services:
     class: Drupal\Core\Extension\ModuleInstaller
     tags:
       - { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator }
-    arguments: ['@app.root', '@module_handler', '@kernel']
+    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 c9fd75d..ea108ef 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;
 
@@ -984,9 +985,22 @@ function drupal_requirements_severity(&$requirements) {
  */
 function drupal_check_module($module) {
   module_load_install($module);
-  // Check requirements
+  // Check requirements based on hook_requirements().
   $requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', array('install'));
-  if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
+  // Make sure the requirements are an array, even if the hook invocation
+  // returns NULL.
+  if (!is_array($requirements)) {
+    $requirements = [];
+  }
+
+  // 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.
+  $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..75466be
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Composer\ExtensionComposerDependencies.
+ */
+
+namespace Drupal\Core\Composer;
+
+use Composer\Semver\Semver;
+use Drupal\Core\Extension\Extension;
+
+/**
+ * Checks whether an extension's Composer dependencies are met.
+ */
+class ExtensionDependencyChecker {
+
+  /**
+   * The path to the Drupal root directory.
+   *
+   * @var string
+   */
+  protected $drupalRoot;
+
+  /**
+   * 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 $drupal_root
+   *   The path to the Drupal root directory.
+   */
+  public function __construct($drupal_root) {
+    $this->drupalRoot = $drupal_root;
+  }
+
+  /**
+   * Determines whether the Composer-based dependencies of an extension are met.
+   *
+   * @param \Drupal\Core\Extension\Extension $extension
+   *   The extension to check.
+   *
+   * @return bool
+   *   TRUE if this extension's Composer dependencies are met, FALSE otherwise.
+   */
+  public function dependenciesAreMet(Extension $extension) {
+    $composer_file_path = $this->drupalRoot . '/' . $extension->getPath() . '/composer.json';
+
+    // If the extension has no Composer file, it has no unmet dependencies.
+    if (!file_exists($composer_file_path)) {
+      return TRUE;
+    }
+
+    $composer_file = json_decode(file_get_contents($composer_file_path));
+
+    // If the extension has no Composer dependencies at all, it has no unmet
+    // dependencies.
+    if (empty($composer_file->require)) {
+      return TRUE;
+    }
+
+    $requirements = (array) $composer_file->require;
+    $installed_packages = $this->getInstalledPackages();
+    $semver = new Semver();
+
+    // Check each required package against the list of installed packages.
+    foreach ($requirements as $package_name => $constraint) {
+      // If a dependency is not installed at all, it is unmet.
+      if (empty($installed_packages[$package_name])) {
+        return FALSE;
+      }
+      // If the wrong version of a dependency is installed, it is unmet.
+      elseif (!$semver->satisfies($installed_packages[$package_name], $constraint)) {
+        return FALSE;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * 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->drupalRoot . '/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..79839ed
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Composer\ExtensionComposerDependenciesRequirements.
+ */
+
+namespace Drupal\Core\Composer;
+
+use Composer\Semver\Semver;
+use Drupal\Core\Extension\Extension;
+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().
+   */
+  public function buildRequirements(array $extensions) {
+    $requirements = [
+      'composer_dependencies' => [
+        'title' => $this->t('Composer dependencies'),
+      ],
+    ];
+    $unmet_dependencies = FALSE;
+    foreach ($extensions as $extension) {
+      if (!$this->dependencyChecker->dependenciesAreMet($extension)) {
+        $unmet_dependencies = TRUE;
+        break;
+      }
+    }
+    if ($unmet_dependencies) {
+      $requirements['composer_dependencies'] += [
+        'description' => $this->t('Some modules have unmet Composer dependencies. Read the <a href="@documentation">documentation on drupal.org</a> on how to install them.', [
+          '@documentation' => 'https://www.drupal.org/node/2627292'
+        ]),
+        'severity' => REQUIREMENT_ERROR,
+      ];
+    }
+    else {
+      $requirements['composer_dependencies'] += [
+        'description' => $this->t('All Composer dependencies have been met.'),
+        'severity' => REQUIREMENT_OK,
+      ];
+    }
+
+    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..7991c1e
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/ExtensionComposerRequirementsException.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Extension\ExtensionComposerRequirementsException.
+ */
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Exception thrown when an extension's Composer requirements are not available.
+ */
+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 has unmet Composer dependencies.");
+  }
+
+  /**
+   * 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 has unmet Composer dependencies.", ['@extension' => $this->extensionName]);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index f8300cb..729d245 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -10,6 +10,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;
@@ -23,6 +24,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
@@ -59,11 +67,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;
@@ -98,9 +109,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->dependenciesAreMet($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.");
@@ -133,6 +150,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 048b9e6..ba75748 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstallerInterface.php
@@ -42,6 +42,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 e8a6cea..f9cffdc 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -12,6 +12,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;
@@ -471,6 +472,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 83fdce9..e91dcda 100644
--- a/core/modules/system/src/Tests/Module/HookRequirementsTest.php
+++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\system\Tests\Module;
 
+use Drupal\Core\Extension\ExtensionComposerRequirementsException;
+
 /**
  * Attempts enabling a module that fails hook_requirements('install').
  *
@@ -28,4 +30,49 @@ function testHookRequirementsFailure() {
     $this->assertText(t('Requirements 1 Test failed requirements'), 'Modules status has been updated.');
     $this->assertModules(array('requirements1_test'), FALSE);
   }
+
+  /**
+   * Tests that a module with uninstalled dependencies is not installable.
+   */
+  function testComposerDependenciesFailure() {
+    $this->assertModules(['composer_uninstallable'], FALSE);
+
+    // Attempt to install the composer_uninstallable module using internals.
+    // This should throw a
+    // \Drupal\Core\Extension\ExtensionComposerRequirementsException.
+    $message = sprintf('Attempting to install composer_uninstallable threw %s.', ExtensionComposerRequirementsException::class);
+    try {
+      $installer = $this->container->get('module_installer');
+      $installer->install(['composer_uninstallable'], TRUE);
+      $this->fail($message);
+    }
+    catch (ExtensionComposerRequirementsException $e) {
+      $this->pass($message);
+    }
+
+    // Attempt to install the composer_uninstallable module using the module
+    // list form. This should fail with a message to the user.
+    $edit = [];
+    $edit['modules[Testing][composer_uninstallable][enable]'] = 'composer_uninstallable';
+    $this->drupalPostForm('admin/modules', $edit, t('Install'));
+
+    $this->assertText('Some modules have unmet Composer dependencies.');
+    // Makes sure the module was NOT installed.
+    $this->assertModules(['composer_uninstallable'], FALSE);
+  }
+
+  /**
+   * Tests that a module with installed dependencies is installable.
+   */
+  function testComposerDependenciesSuccess() {
+    $this->assertModules(['composer_installable'], FALSE);
+
+    // Attempt to install the composer_installable module.
+    $edit = [];
+    $edit['modules[Testing][composer_installable][enable]'] = 'composer_installable';
+    $this->drupalPostForm('admin/modules', $edit, t('Install'));
+
+    // Makes sure the module was installed.
+    $this->assertModules(['composer_installable'], TRUE);
+  }
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index afa5892..aadf377 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -239,6 +239,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_installable/composer.json b/core/modules/system/tests/modules/composer_installable/composer.json
new file mode 100644
index 0000000..04a5827
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_installable/composer.json
@@ -0,0 +1,6 @@
+{
+  "name": "drupal_test/composer_installable",
+  "require": {
+    "psr/log": "~1.0"
+  }
+}
diff --git a/core/modules/system/tests/modules/composer_installable/composer_installable.info.yml b/core/modules/system/tests/modules/composer_installable/composer_installable.info.yml
new file mode 100644
index 0000000..399e267
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_installable/composer_installable.info.yml
@@ -0,0 +1,6 @@
+name: 'Composer Installable'
+type: module
+description: 'Test module that is installable because its composer dependencies have already been installed'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/composer_uninstallable/composer.json b/core/modules/system/tests/modules/composer_uninstallable/composer.json
new file mode 100644
index 0000000..be1a94a
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_uninstallable/composer.json
@@ -0,0 +1,6 @@
+{
+  "name": "drupal_test/composer_uninstallable",
+  "require": {
+    "scalopus/empty": "^1.1"
+  }
+}
diff --git a/core/modules/system/tests/modules/composer_uninstallable/composer_uninstallable.info.yml b/core/modules/system/tests/modules/composer_uninstallable/composer_uninstallable.info.yml
new file mode 100644
index 0000000..a7f734e
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_uninstallable/composer_uninstallable.info.yml
@@ -0,0 +1,6 @@
+name: 'Composer Uninstallable'
+type: module
+description: 'Test module that is not installable because of missing composer dependencies.'
+package: Testing
+version: VERSION
+core: 8.x
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..f2856ab
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Composer\ExtensionDependencyCheckerTest.
+ */
+
+namespace Drupal\Tests\Core\Composer;
+
+use Drupal\Core\Composer\ExtensionDependencyChecker;
+use Drupal\Core\Extension\Extension;
+use Drupal\Tests\UnitTestCase;
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Composer\ExtensionDependencyChecker
+ *
+ * @group Composer
+ */
+class ExtensionDependencyCheckerTest extends UnitTestCase {
+
+  /**
+   * 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.
+   */
+  public function providerDependenciesAreMet() {
+    return [
+      // No Composer dependencies.
+      [TRUE, NULL, NULL],
+      // No Composer dependencies, without any installed packages.
+      [TRUE, NULL, '[]'],
+      // No Composer dependencies, with an empty extension Composer file.
+      [TRUE, '{}', '[]'],
+      // One met dependency using different version constraints.
+      [TRUE, '{"require": {"vendor/package":"1.0.0"}}', '[{"name":"vendor/package","version_normalized":"1.0.0"}]'],
+      [TRUE, '{"require": {"vendor/package":"~1.0"}}', '[{"name":"vendor/package","version_normalized":"1.0.0"}]'],
+      // One unmet dependency, without any installed packages.
+      [FALSE, '{"require": {"vendor/not-installed":"1.0.0"}}', '[]'],
+      // One unmet dependency, with another package installed.
+      [FALSE, '{"require": {"vendor/not-installed":"1.0.0"}}', '[{"name":"vendor/package","version_normalized":"1.0.0"}]'],
+      // One unmet dependency using different version constraints.
+      [FALSE, '{"require": {"vendor/package":"1.1.1"}}', '[{"name":"vendor/package","version_normalized":"1.0.0"}]'],
+      [FALSE, '{"require": {"vendor/package":"~1.1"}}', '[{"name":"vendor/package","version_normalized":"1.0.0"}]'],
+      // One unmet require-dev dependency, which is ignored, because it is not
+      // required for normal site operation.
+      [TRUE, '{"require-dev": {"vendor/package":"~1.1"}}', NULL],
+      [TRUE, '{"require-dev": {"vendor/package":"1.1.1"}}', '[{"name":"vendor/package","version_normalized":"1.0.0"}]'],
+      [TRUE, '{"require-dev": {"vendor/package":"~1.1"}}', '[{"name":"vendor/package","version_normalized":"1.0.0"}]'],
+    ];
+  }
+
+  /**
+   * @covers ::dependenciesAreMet
+   * @covers ::getInstalledPackages
+   *
+   * @dataProvider providerDependenciesAreMet
+   */
+  public function testDependenciesAreMet($expected, $composer_json, $installed_json) {
+    $structure = [
+      'modules' => ['some_module' => ['some_module.info.yml' => '']],
+      'vendor' => ['composer' => []],
+    ];
+    if ($installed_json !== NULL) {
+      $structure['vendor']['composer']['installed.json'] = $installed_json;
+    }
+    if ($composer_json !== NULL) {
+      $structure['modules']['some_module']['composer.json'] = $composer_json;
+    }
+    vfsStream::setup('root', NULL, $structure);
+
+    $extension = new Extension(vfsStream::url('root'), 'module', 'modules/some_module/some_module.info.yml');
+    $sut = new ExtensionDependencyChecker(vfsStream::url('root'));
+
+    $this->assertEquals($expected, $sut->dependenciesAreMet($extension));
+  }
+
+}
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..1960315
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Composer\ExtensionDependencyRequirementsTest.
+ */
+
+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.
+   *   - A boolean TRUE if dependencies are met, FALSE otherwise.
+   */
+  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.
+      [2, FALSE],
+      // REQUIREMENT_OK is 0.
+      [0, TRUE],
+    ];
+  }
+
+  /**
+   * @covers ::buildRequirements
+   *
+   * @dataProvider providerBuildRequirements
+   *
+   * @param int $expected_severity
+   *   One of the REQUIREMENT_* constants.
+   * @param bool $dependencies_met
+   *   Whether the extension's dependencies have been met.
+   */
+  public function testBuildRequirements($expected_severity, $dependencies_met) {
+    $dependency_checker = $this->getMockBuilder(ExtensionDependencyChecker::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+    $dependency_checker->expects($this->once())
+      ->method('dependenciesAreMet')
+      ->willReturn($dependencies_met);
+
+    $string_translation = $this->getMock(TranslationInterface::class);
+
+    $extension = $this->getMockBuilder(Extension::class)
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $sut = new ExtensionDependencyRequirements($dependency_checker, $string_translation);
+
+    $requirements = $sut->buildRequirements([$extension]);
+    $this->assertCount(1, $requirements);
+    $this->assertArrayHasKey('composer_dependencies', $requirements);
+    $this->assertArrayHasKey('severity', $requirements['composer_dependencies']);
+    $this->assertSame($expected_severity, $requirements['composer_dependencies']['severity']);
+  }
+
+}
