diff --git a/core/core.services.yml b/core/core.services.yml
index 6a31a44..dcf0578 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
@@ -473,7 +481,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 bae87d7..c076495 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;
 
@@ -982,9 +983,18 @@ 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) {
+
+  // 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..a655952
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyChecker.php
@@ -0,0 +1,139 @@
+<?php
+
+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 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;
+  }
+
+  /**
+   * Determines whether the Composer-based dependencies of an extension are met.
+   *
+   * Platform requirements and require-dev are ignored.
+   *
+   * @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) {
+    return empty($this->getUnmetDependencies($extension));
+  }
+
+  /**
+   * Gets a list of all the unmet Composer-based dependencies for an extension.
+   *
+   * @param \Drupal\Core\Extension\Extension $extension
+   *   An extension to check.
+   *
+   * @return string[]
+   *   An array of unmet dependencies, or empty array. Keys are package names,
+   *   values are version constraints. Similar to the 'require' section of a
+   *   composer.json file.
+   */
+  public function getUnmetDependencies(Extension $extension) {
+    $composer_file_path = $this->appRoot . '/' . $extension->getPath() . '/composer.json';
+
+    // If the extension has no Composer file, it has no unmet dependencies.
+    if (!file_exists($composer_file_path)) {
+      return [];
+    }
+
+    $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 [];
+    }
+
+    $requirements = (array) $composer_file->require;
+    $installed_packages = $this->getInstalledPackages();
+    $semver = new Semver();
+    $unmet_dependencies = [];
+
+    // Check each required package against the list of installed packages.
+    foreach ($requirements as $package_name => $constraint) {
+      // If the package is a platform requirement, we ignore it.
+      if (strpos($package_name, '/') === FALSE) {
+        continue;
+      }
+      // If a dependency is not installed at all, it is unmet.
+      if (empty($installed_packages[$package_name])) {
+        $unmet_dependencies[$package_name] = $constraint;
+      }
+      // If the wrong version of a dependency is installed, it is unmet.
+      elseif (!$semver->satisfies($installed_packages[$package_name], $constraint)) {
+        $unmet_dependencies[$package_name] = $constraint;
+      }
+    }
+    return $unmet_dependencies;
+  }
+
+  /**
+   * 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..5708f8f
--- /dev/null
+++ b/core/lib/Drupal/Core/Composer/ExtensionDependencyRequirements.php
@@ -0,0 +1,95 @@
+<?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('Composer dependencies'),
+      ],
+    ];
+    $unmet_dependencies = [];
+    foreach ($extensions as $extension) {
+      $unmet = $this->dependencyChecker->getUnmetDependencies($extension);
+      if (!empty($unmet)) {
+        $unmet_dependencies[] = $extension->getName() . ' (' . $this->formatComposerDependencies($unmet) . ')';
+      }
+    }
+    if (!empty($unmet_dependencies)) {
+      $requirements['composer_dependencies'] += [
+        'description' => $this->t('The following extensions have unmet Composer-based dependencies: @extensions. Read the <a href=":documentation">documentation on drupal.org</a> on how to install them.', [
+          '@extensions' => implode(', ', $unmet_dependencies),
+          ':documentation' => 'https://www.drupal.org/documentation/install/composer-dependencies',
+        ]),
+        'severity' => REQUIREMENT_ERROR,
+      ];
+    }
+    else {
+      $requirements['composer_dependencies'] += [
+        'description' => $this->t('All Composer dependencies have been met.'),
+        'severity' => REQUIREMENT_OK,
+      ];
+    }
+    return $requirements;
+  }
+
+  /**
+   * Format some dependencies so the user can understand them.
+   *
+   * @param string[] $dependencies
+   *   An array where the key is the name of the package and the value is the
+   *   constraint.
+   *
+   * @return string
+   *   User-readable string.
+   */
+  protected function formatComposerDependencies($dependencies) {
+    $result = [];
+    foreach($dependencies as $name => $constraint) {
+      $result[] = $name . ': ' . $constraint;
+    }
+    return implode(', ', $result);
+  }
+
+}
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 b2223b7..cc706f0 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->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.");
@@ -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 14875c6..1bc7208 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 0cd54f3..d6d6924 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 a589a8c..760e423 100644
--- a/core/modules/system/src/Tests/Module/HookRequirementsTest.php
+++ b/core/modules/system/src/Tests/Module/HookRequirementsTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\system\Tests\Module;
 
+use Drupal\Core\Extension\ExtensionComposerRequirementsException;
+
 /**
  * Attempts enabling a module that fails hook_requirements('install').
  *
@@ -23,4 +25,50 @@ 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.
+   */
+  public 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('The following extensions have unmet Composer-based dependencies: composer_uninstallable (scalopus/empty: ^1.1, jellyfish/empty: ~2.0, ordinal/empty: 4.0.*). Read the documentation on drupal.org on how to install them.');
+    // Makes sure the module was NOT installed.
+    $this->assertModules(['composer_uninstallable'], FALSE);
+  }
+
+  /**
+   * Tests that a module with installed dependencies is installable.
+   */
+  public 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 44212b1..9918b97 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..9e56b74
--- /dev/null
+++ b/core/modules/system/tests/modules/composer_uninstallable/composer.json
@@ -0,0 +1,8 @@
+{
+  "name": "drupal_test/composer_uninstallable",
+  "require": {
+    "scalopus/empty": "^1.1",
+    "jellyfish/empty": "~2.0",
+    "ordinal/empty": "4.0.*"
+  }
+}
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..0022e78
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyCheckerTest.php
@@ -0,0 +1,131 @@
+<?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;
+
+/**
+ * @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.
+      'no dependencies' => [
+        TRUE,
+        NULL,
+        NULL,
+      ],
+      // No Composer dependencies, without any installed packages.
+      'no dependencies without installed packages' => [
+        TRUE,
+        NULL,
+        '[]',
+      ],
+      // No Composer dependencies, with an empty extension Composer file.
+      'no dependencies with empty composer.json' => [
+        TRUE,
+        '{}',
+        '[]',
+      ],
+      // One met dependency using different version constraints.
+      'one met dependency with specific version constraint' => [
+        TRUE,
+        '{"require": {"vendor/package":"1.0.0"}}',
+        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
+      ],
+      'one met dependency with compatible version constraint' => [
+        TRUE,
+        '{"require": {"vendor/package":"~1.0"}}',
+        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
+      ],
+      // One unmet dependency, without any installed packages.
+      'one unmet dependency dependency without installed packages' => [
+        FALSE,
+        '{"require": {"vendor/not-installed":"1.0.0"}}',
+        '[]',
+      ],
+      // One unmet dependency, with another package installed.
+      'one unmet dependency with installed packages' => [
+        FALSE,
+        '{"require": {"vendor/not-installed":"1.0.0"}}',
+        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
+      ],
+      // One unmet dependency using different version constraints.
+      'one unmet dependency with specific version constraint' => [
+        FALSE,
+        '{"require": {"vendor/package":"1.1.1"}}',
+        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
+      ],
+      'one unmet dependency with compatible version constraint' => [
+        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.
+      'one unmet require-dev dependency with specific version constraint without installed packages' => [
+        TRUE,
+        '{"require-dev": {"vendor/package":"~1.1"}}',
+        NULL,
+      ],
+      'one unmet require-dev dependency with compatible version constraint with installed packages' => [
+        TRUE,
+        '{"require-dev": {"vendor/package":"1.1.1"}}',
+        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
+      ],
+      'one unmet require-dev dependency with compatible version constraint' => [
+        TRUE,
+        '{"require-dev": {"vendor/package":"~1.1"}}',
+        '[{"name":"vendor/package","version_normalized":"1.0.0"}]'
+      ],
+      // Platform dependencies are ignored.
+      'ignore platform dependencies' => [
+        TRUE,
+        '{"require": {"php":">10.0"}}',
+        '[{"name":"php","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..dd4fc1f
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Composer/ExtensionDependencyRequirementsTest.php
@@ -0,0 +1,74 @@
+<?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,
+        [
+          'scalopus/empty' => '^1.1',
+        ],
+      ],
+      // REQUIREMENT_OK is 0.
+      'REQUIREMENT_OK' => [
+        0,
+        [],
+      ],
+    ];
+  }
+
+  /**
+   * @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, $unmet_dependencies) {
+    $extension = $this->prophesize(Extension::class);
+
+    $dependency_checker = $this->prophesize(ExtensionDependencyChecker::class);
+    $dependency_checker->getUnmetDependencies($extension->reveal())->willReturn($unmet_dependencies);
+
+    $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']);
+  }
+
+}
