diff --git a/package_manager/tests/fixtures/distro_core/composer.json b/package_manager/tests/fixtures/distro_core/composer.json
deleted file mode 100644
index a7a8274cb6e5e850fba380e6670fbeb8b33a7cbd..0000000000000000000000000000000000000000
--- a/package_manager/tests/fixtures/distro_core/composer.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-    "require": {
-        "drupal/test-distribution": "*"
-    },
-    "extra": {
-        "_comment": [
-            "This is a fake composer.json simulating a site which requires a distribution.",
-            "The required core packages are determined by scanning the lock file.",
-            "The fake distribution requires Drupal core directly."
-        ]
-    }
-}
diff --git a/package_manager/tests/fixtures/distro_core/composer.lock b/package_manager/tests/fixtures/distro_core/composer.lock
deleted file mode 100644
index 56572fd029d8f859be438b6fa40b76ebbc9daf4c..0000000000000000000000000000000000000000
--- a/package_manager/tests/fixtures/distro_core/composer.lock
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-    "packages": [
-        {
-            "name": "drupal/test-distribution",
-            "version": "1.0.0",
-            "require": {
-                "drupal/core": "*"
-            }
-        },
-        {
-            "name": "drupal/core",
-            "version": "9.8.0"
-        }
-    ],
-    "packages-dev": []
-}
diff --git a/package_manager/tests/fixtures/distro_core/vendor/composer/installed.json b/package_manager/tests/fixtures/distro_core/vendor/composer/installed.json
deleted file mode 100644
index 33e328278743cbba936dc9dd4d6a1d84c26d16fd..0000000000000000000000000000000000000000
--- a/package_manager/tests/fixtures/distro_core/vendor/composer/installed.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-    "packages": [
-        {
-            "name": "drupal/test-distribution",
-            "version": "1.0.0",
-            "require": {
-                "drupal/core": "*"
-            }
-        },
-        {
-            "name": "drupal/core",
-            "version": "9.8.0"
-        }
-    ]
-}
diff --git a/package_manager/tests/fixtures/distro_core_recommended/composer.json b/package_manager/tests/fixtures/distro_core_recommended/composer.json
deleted file mode 100644
index 3a62074ca2371426bbf177a0b941f808fbbfae3a..0000000000000000000000000000000000000000
--- a/package_manager/tests/fixtures/distro_core_recommended/composer.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-    "require": {
-        "drupal/test-distribution": "*"
-    },
-    "extra": {
-        "_comment": [
-            "This is a fake composer.json simulating a site which requires a distribution.",
-            "The required core packages are determined by scanning the lock file.",
-            "The fake distribution uses drupal/core-recommended to require Drupal core."
-        ]
-    }
-}
diff --git a/package_manager/tests/fixtures/distro_core_recommended/composer.lock b/package_manager/tests/fixtures/distro_core_recommended/composer.lock
deleted file mode 100644
index dd29a0514d0ebdfc87484b52fc0c3256045ccb85..0000000000000000000000000000000000000000
--- a/package_manager/tests/fixtures/distro_core_recommended/composer.lock
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-    "packages": [
-        {
-            "name": "drupal/test-distribution",
-            "version": "1.0.0",
-            "require": {
-                "drupal/core-recommended": "*"
-            }
-        },
-        {
-            "name": "drupal/core-recommended",
-            "version": "9.8.0",
-            "require": {
-                "drupal/core": "9.8.0"
-            }
-        },
-        {
-            "name": "drupal/core",
-            "version": "9.8.0"
-        }
-    ],
-    "packages-dev": []
-}
diff --git a/package_manager/tests/fixtures/distro_core_recommended/vendor/composer/installed.json b/package_manager/tests/fixtures/distro_core_recommended/vendor/composer/installed.json
deleted file mode 100644
index f68c6fc621d1c03b5d3d2f0d52396342a0bf92f3..0000000000000000000000000000000000000000
--- a/package_manager/tests/fixtures/distro_core_recommended/vendor/composer/installed.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-    "packages": [
-        {
-            "name": "drupal/test-distribution",
-            "version": "1.0.0",
-            "require": {
-                "drupal/core-recommended": "*"
-            }
-        },
-        {
-            "name": "drupal/core-recommended",
-            "version": "9.8.0",
-            "require": {
-                "drupal/core": "9.8.0"
-            }
-        },
-        {
-            "name": "drupal/core",
-            "version": "9.8.0"
-        }
-    ]
-}
diff --git a/package_manager/tests/src/Kernel/ComposerUtilityTest.php b/package_manager/tests/src/Kernel/ComposerUtilityTest.php
index fa4e1d1006d323351a0b32499b048b7f2494908d..6046c781d59e30a77682dc409f6b81ea7be16623 100644
--- a/package_manager/tests/src/Kernel/ComposerUtilityTest.php
+++ b/package_manager/tests/src/Kernel/ComposerUtilityTest.php
@@ -43,49 +43,6 @@ class ComposerUtilityTest extends KernelTestBase {
     $this->assertFileDoesNotExist($dir . '/.htaccess');
   }
 
-  /**
-   * Data provider for testCorePackagesFromLockFile().
-   *
-   * @return string[][]
-   *   The test cases.
-   */
-  public function providerCorePackagesFromLockFile(): array {
-    $fixtures_dir = __DIR__ . '/../../fixtures';
-
-    return [
-      'distro with drupal/core-recommended' => [
-        // This fixture's lock file mentions drupal/core, which is considered a
-        // canonical core package, but it will be ignored in favor of
-        // drupal/core-recommended, which always requires drupal/core as one of
-        // its direct dependencies.
-        "$fixtures_dir/distro_core_recommended",
-        ['drupal/core-recommended'],
-      ],
-      'distro with drupal/core' => [
-        "$fixtures_dir/distro_core",
-        ['drupal/core'],
-      ],
-    ];
-  }
-
-  /**
-   * Tests that required core packages are found by scanning the lock file.
-   *
-   * @param string $dir
-   *   The path of the fake site fixture.
-   * @param string[] $expected_packages
-   *   The names of the core packages which should be detected.
-   *
-   * @covers ::getCorePackages
-   *
-   * @dataProvider providerCorePackagesFromLockFile
-   */
-  public function testCorePackagesFromLockFile(string $dir, array $expected_packages): void {
-    $packages = ComposerUtility::createForDirectory($dir)
-      ->getCorePackages();
-    $this->assertSame($expected_packages, array_keys($packages));
-  }
-
   /**
    * @covers ::getPackagesNotIn
    * @covers ::getPackagesWithDifferentVersionsIn
diff --git a/package_manager/tests/src/Unit/ComposerUtilityTest.php b/package_manager/tests/src/Unit/ComposerUtilityTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2f6762fd7ae93e997de8006830e3f3101453dc6e
--- /dev/null
+++ b/package_manager/tests/src/Unit/ComposerUtilityTest.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\Tests\package_manager\Unit;
+
+use Composer\Package\PackageInterface;
+use Drupal\package_manager\ComposerUtility;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\package_manager\ComposerUtility
+ *
+ * @group package_manager
+ */
+class ComposerUtilityTest extends UnitTestCase {
+
+  /**
+   * Data provider for ::testCorePackages().
+   *
+   * @return \string[][][]
+   *   The test cases.
+   */
+  public function providerCorePackages(): array {
+    return [
+      'core-recommended not installed' => [
+        ['drupal/core'],
+        ['drupal/core'],
+      ],
+      'core-recommended installed' => [
+        ['drupal/core', 'drupal/core-recommended'],
+        ['drupal/core-recommended'],
+      ],
+    ];
+  }
+
+  /**
+   * @covers ::getCorePackages
+   *
+   * @param string[] $installed_package_names
+   *   The names of the packages that are installed.
+   * @param string[] $expected_core_package_names
+   *   The expected core package names that should be returned by
+   *   ::getCorePackages().
+   *
+   * @dataProvider providerCorePackages
+   */
+  public function testCorePackages(array $installed_package_names, array $expected_core_package_names): void {
+    $versions = array_fill(0, count($installed_package_names), '1.0.0');
+    $installed_packages = array_combine($installed_package_names, $versions);
+
+    $core_packages = $this->mockUtilityWithPackages($installed_packages)
+      ->getCorePackages();
+    $this->assertSame($expected_core_package_names, array_keys($core_packages));
+  }
+
+  /**
+   * Mocks a ComposerUtility object to return a set of installed packages.
+   *
+   * @param string[]|null[] $installed_packages
+   *   The installed packages that the mocked object should return. The keys are
+   *   the package names and the values are either a version number or NULL to
+   *   not mock the corresponding package's getVersion() method.
+   *
+   * @return \Drupal\package_manager\ComposerUtility|\PHPUnit\Framework\MockObject\MockObject
+   *   The mocked object.
+   */
+  private function mockUtilityWithPackages(array $installed_packages) {
+    $mock = $this->getMockBuilder(ComposerUtility::class)
+      ->disableOriginalConstructor()
+      ->onlyMethods(['getInstalledPackages'])
+      ->getMock();
+
+    $packages = [];
+    foreach ($installed_packages as $name => $version) {
+      $package = $this->createMock(PackageInterface::class);
+      if (isset($version)) {
+        $package->method('getVersion')->willReturn($version);
+      }
+      $packages[$name] = $package;
+    }
+    $mock->method('getInstalledPackages')->willReturn($packages);
+
+    return $mock;
+  }
+
+}