diff --git a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
index c7848e2..931da49 100644
--- a/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
+++ b/core/lib/Drupal/Core/Extension/ExtensionDiscovery.php
@@ -78,7 +78,7 @@ class ExtensionDiscovery {
   protected $profileDirectories;
 
   /**
-   * The app root.
+   * The app root for the current operation.
    *
    * @var string
    */
@@ -188,14 +188,8 @@ public function scan($type, $include_tests = NULL) {
 
     // Find the site-specific directory to search. Since we are using this
     // method to discover extensions including profiles, we might be doing this
-    // at install time. Therefore Kernel service is not always available, but is
-    // preferred.
-    if (\Drupal::hasService('kernel')) {
-      $searchdirs[static::ORIGIN_SITE] = \Drupal::service('site.path');
-    }
-    else {
-      $searchdirs[static::ORIGIN_SITE] = $this->sitePath ?: DrupalKernel::findSitePath(Request::createFromGlobals());
-    }
+    // at install time.
+    $searchdirs[static::ORIGIN_SITE] = $this->sitePath ?: DrupalKernel::findSitePath(Request::createFromGlobals());
 
     // Unless an explicit value has been passed, manually check whether we are
     // in a test environment, in which case test extensions must be included.
@@ -208,12 +202,12 @@ public function scan($type, $include_tests = NULL) {
     $files = array();
     foreach ($searchdirs as $dir) {
       // Discover all extensions in the directory, unless we did already.
-      if (!isset(static::$files[$dir][$include_tests])) {
-        static::$files[$dir][$include_tests] = $this->scanDirectory($dir, $include_tests);
+      if (!isset(static::$files[$this->root][$dir][$include_tests])) {
+        static::$files[$this->root][$dir][$include_tests] = $this->scanDirectory($dir, $include_tests);
       }
       // Only return extensions of the requested type.
-      if (isset(static::$files[$dir][$include_tests][$type])) {
-        $files += static::$files[$dir][$include_tests][$type];
+      if (isset(static::$files[$this->root][$dir][$include_tests][$type])) {
+        $files += static::$files[$this->root][$dir][$include_tests][$type];
       }
     }
 
diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ExtensionDiscoveryTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ExtensionDiscoveryTest.php
new file mode 100644
index 0000000..fa95c1e
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Extension/ExtensionDiscoveryTest.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Extension;
+
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Extension\Extension;
+use Drupal\KernelTests\KernelTestBase;
+use org\bovigo\vfs\vfsStream;
+
+/**
+ * Test the behavior of ExtensionDiscovery.
+ *
+ * @coversDefaultClass \Drupal\Core\Extension\ExtensionDiscovery
+ *
+ * @group Extension
+ *
+ * @todo: Turn this into a UnitTestCase when ExtensionDiscovery::scan() doesn't
+ *    depend on \drupal_valid_test_ua().
+ */
+class ExtensionDiscoveryTest extends KernelTestBase {
+
+  protected function getFileSystem() {
+    return [
+      'sites' => [
+        'default' => [
+          'modules' => [
+          ],
+          'themes' => [
+          ],
+        ],
+        'all' => [
+          'modules' => [
+          ],
+          'themes' => [
+          ],
+        ],
+      ],
+      'modules' => [
+      ],
+      'themes' => [
+      ],
+    ];
+  }
+
+  /**
+   * Provides some test data to verify the scan() method.
+   *
+   * @return string[]
+   *   - The expected path to the extension.
+   *   - The expected extension name to find.
+   *   - An array representing the file system to scan.
+   *   - The type of extension to scan for.
+   */
+  public function providerScan() {
+    $module_info = <<<'EOS'
+type: module
+name: Module
+core: 8.x
+EOS;
+    $theme_info = <<<'EOS'
+type: theme
+base theme: false
+name: Theme
+EOS;
+
+    // Set up the filesystem.
+    $filesystem = $this->getFileSystem();
+
+    // Add modules.
+    $filesystem['sites']['all']['modules']['module_sites_all']['module_sites_all.info.yml'] = $module_info;
+    $filesystem['sites']['default']['modules']['module_sites_default']['module_sites_default.info.yml'] = $module_info;
+    $filesystem['modules']['module_modules']['module_modules.info.yml'] = $module_info;
+
+    // Add themes.
+    $filesystem['sites']['all']['themes']['theme_sites_all']['theme_sites_all.info.yml'] = $theme_info;
+    $filesystem['sites']['default']['themes']['theme_sites_default']['theme_sites_default.info.yml'] = $theme_info;
+    $filesystem['themes']['theme_themes']['theme_themes.info.yml'] = $theme_info;
+
+    return [
+      // Modules.
+      ['sites/all/modules', 'module_sites_all', $filesystem, 'module'],
+      ['sites/default/modules', 'module_sites_default', $filesystem, 'module'],
+      ['modules', 'module_modules', $filesystem, 'module'],
+      // Themes.
+      ['sites/all/themes', 'theme_sites_all', $filesystem, 'theme'],
+      ['sites/default/themes', 'theme_sites_default', $filesystem, 'theme'],
+      ['themes', 'theme_themes', $filesystem, 'theme'],
+    ];
+  }
+
+  /**
+   * Show that we can scan for a module and find it.
+   *
+   * @covers ::scan
+   *
+   * @dataProvider providerScan
+   */
+  public function testScan($expected_path, $expected_name, $filesystem, $extension_type) {
+    // Set up the file system.
+    vfsStream::setup('root', NULL, $filesystem);
+    $root = vfsStream::url('root');
+
+    // Create an ExtensionDiscovery with $root.
+    $extension_discovery = new ExtensionDiscovery($root, TRUE, ['no/profile'], 'sites/default');
+
+    // Perform the scan.
+    $modules = $extension_discovery->scan($extension_type);
+
+    // We should have found the module. Now we can assert its attributes.
+    $this->assertArrayHasKey($expected_name, $modules);
+    /* @var $module \Drupal\Core\Extension\Extension */
+    $module = $modules[$expected_name];
+    $this->assertInstanceOf(Extension::class, $module);
+    $this->assertEquals($extension_type, $module->getType());
+    $this->assertEquals($expected_name, $module->getName());
+    $this->assertEquals($expected_path . '/' . $expected_name, $module->getPath());
+
+    // We need to assert the root path of the extension, but it does not have
+    // an accessor.
+    $ref_root = new \ReflectionProperty($module, 'root');
+    $ref_root->setAccessible(TRUE);
+    $this->assertEquals(vfsStream::url('root'), $ref_root->getValue($module));
+  }
+
+  /**
+   * Check that the cache uses different keys for different root directories.
+   *
+   * @covers ::scan
+   */
+  public function testScanCacheKeys() {
+    vfsStream::setup('root');
+
+    // Create the same filesystem in two different directories. We don't
+    // actually need an array of directories because the cache should behave the
+    // same without them.
+    vfsStream::create([], vfsStream::newDirectory('test1'));
+    vfsStream::create([], vfsStream::newDirectory('test2'));
+
+    $root_1 = vfsStream::url('root/test1');
+    $root_2 = vfsStream::url('root/test2');
+
+    // Create an ExtensionDiscovery with $root_1.
+    $extension_discovery = new ExtensionDiscovery($root_1, TRUE, ['no/profile'], 'sites/default');
+
+    // Assert that the cache does not contain either key we're looking for until
+    // after we've performed a scan.
+    $this->assertArrayNotHasKey($root_1, $this->getDiscoveredCache($extension_discovery));
+    $this->assertArrayNotHasKey($root_2, $this->getDiscoveredCache($extension_discovery));
+
+    // Perform the scan.
+    $extension_discovery->scan('module');
+
+    // Assert that we have $root_1 as a key, but not $root_2 yet.
+    $this->assertArrayHasKey($root_1, $this->getDiscoveredCache($extension_discovery));
+    $this->assertArrayNotHasKey($root_2, $this->getDiscoveredCache($extension_discovery));
+
+    // Re-instantiate ExtensionDiscovery with a different root.
+    $extension_discovery = new ExtensionDiscovery($root_2, TRUE, ['no/profile'], 'sites/default');
+
+    // Since the cache is static, it will still only have $root_1.
+    $this->assertArrayHasKey($root_1, $this->getDiscoveredCache($extension_discovery));
+    $this->assertArrayNotHasKey($root_2, $this->getDiscoveredCache($extension_discovery));
+
+    // Perform the scan.
+    $extension_discovery->scan('module');
+
+    // Now we have both keys in the cache array.
+    $this->assertArrayHasKey($root_1, $this->getDiscoveredCache($extension_discovery));
+    $this->assertArrayHasKey($root_2, $this->getDiscoveredCache($extension_discovery));
+  }
+
+  /**
+   * Helper method to get the cache array from an ExtensionDiscovery object.
+   *
+   * @param ExtensionDiscovery $extension_discovery
+   *   An ExtensionDiscovery object.
+   *
+   * @return array[]
+   *   The files array.
+   */
+  protected function getDiscoveredCache(ExtensionDiscovery $extension_discovery) {
+    $ref_cache = new \ReflectionProperty(ExtensionDiscovery::class, 'files');
+    $ref_cache->setAccessible(TRUE);
+    return $ref_cache->getValue($extension_discovery);
+  }
+
+}
