diff --git a/core/modules/update/tests/src/Functional/UpdateContribTest.php b/core/modules/update/tests/src/Functional/UpdateContribTest.php
index 294074c592e92f47bd51a8751e0d4602cd8e3e8f..41f20ccff2ab22485fe2f3fb38e35d0e9ed9a51e 100644
--- a/core/modules/update/tests/src/Functional/UpdateContribTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateContribTest.php
@@ -11,6 +11,7 @@
  * @group update
  */
 class UpdateContribTest extends UpdateTestBase {
+  use UpdateTestTrait;
 
   /**
    * {@inheritdoc}
@@ -53,17 +54,14 @@ protected function setUp(): void {
    * Tests when there is no available release data for a contrib module.
    */
   public function testNoReleasesAvailable() {
-    $system_info = [
-      '#all' => [
-        'version' => '8.0.0',
-      ],
+    $this->mockInstalledExtensionsInfo([
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
     $this->refreshUpdateStatus(['drupal' => '0.0', 'aaa_update_test' => 'no-releases']);
     // Cannot use $this->standardTests() because we need to check for the
     // 'No available releases found' string.
@@ -85,17 +83,15 @@ public function testNoReleasesAvailable() {
    * Tests the basic functionality of a contrib module on the status report.
    */
   public function testUpdateContribBasic() {
-    $system_info = [
-      '#all' => [
-        'version' => '8.0.0',
-      ],
+    $installed_extensions = [
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
     ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    $this->mockInstalledExtensionsInfo($installed_extensions);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
     $this->refreshUpdateStatus(
       [
         'drupal' => '0.0',
@@ -111,8 +107,9 @@ public function testUpdateContribBasic() {
 
     // Since aaa_update_test is installed the fact it is hidden and in the
     // Testing package means it should not appear.
-    $system_info['aaa_update_test']['hidden'] = TRUE;
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    $installed_extensions['aaa_update_test']['hidden'] = TRUE;
+    $this->mockInstalledExtensionsInfo($installed_extensions);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
     $this->refreshUpdateStatus(
       [
         'drupal' => '0.0',
@@ -123,8 +120,9 @@ public function testUpdateContribBasic() {
     $this->assertSession()->linkByHrefNotExists('http://example.com/project/aaa_update_test');
 
     // A hidden and installed project not in the Testing package should appear.
-    $system_info['aaa_update_test']['package'] = 'aaa_update_test';
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    $installed_extensions['aaa_update_test']['package'] = 'aaa_update_test';
+    $this->mockInstalledExtensionsInfo($installed_extensions);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
     $this->refreshUpdateStatus(
       [
         'drupal' => '0.0',
@@ -150,12 +148,9 @@ public function testUpdateContribBasic() {
    */
   public function testUpdateContribOrder() {
     // We want core to be version 8.0.0.
-    $system_info = [
-      '#all' => [
-        'version' => '8.0.0',
-      ],
-      // All the rest should be visible as contrib modules at version 8.x-1.0.
-
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
+    // All the rest should be visible as contrib modules at version 8.x-1.0.
+    $this->mockInstalledExtensionsInfo([
       // aaa_update_test needs to be part of the "CCC Update test" project,
       // which would throw off the report if we weren't properly sorting by
       // the project names.
@@ -179,8 +174,7 @@ public function testUpdateContribOrder() {
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
     $this->refreshUpdateStatus(['drupal' => '0.0', '#all' => '1_0']);
     $this->standardTests();
     // We're expecting the report to say all projects are up to date.
@@ -222,25 +216,20 @@ public function testUpdateBaseThemeSecurityUpdate() {
     \Drupal::service('theme_installer')->install(['update_test_subtheme']);
 
     // Define the initial state for core and the subtheme.
-    $system_info = [
-      // We want core to be version 8.0.0.
-      '#all' => [
-        'version' => '8.0.0',
-      ],
-      // Show the update_test_basetheme
+    $this->mockInstalledExtensionsInfo([
+      // Show the update_test_basetheme.
       'update_test_basetheme' => [
         'project' => 'update_test_basetheme',
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-      // Show the update_test_subtheme
+      // Show the update_test_subtheme.
       'update_test_subtheme' => [
         'project' => 'update_test_subtheme',
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
     $xml_mapping = [
       'drupal' => '0.0',
       'update_test_subtheme' => '1_0',
@@ -261,14 +250,13 @@ public function testNormalUpdateAvailable() {
     $this->drupalGet('admin/reports/updates/check');
     $assert_session->statusCodeEquals(403);
 
-    $system_info = [
+    $this->mockInstalledExtensionsInfo([
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
 
     foreach (['1.1', '1.2', '2.0'] as $version) {
       foreach (['-beta1', '-alpha1', ''] as $extra_version) {
@@ -363,11 +351,7 @@ public function testUpdateShowDisabledThemes() {
     $extension_config->save();
 
     // Define the initial state for core and the test contrib themes.
-    $system_info = [
-      // We want core to be version 8.0.0.
-      '#all' => [
-        'version' => '8.0.0',
-      ],
+    $this->mockInstalledExtensionsInfo([
       // The update_test_basetheme should be visible and up to date.
       'update_test_basetheme' => [
         'project' => 'update_test_basetheme',
@@ -380,13 +364,12 @@ public function testUpdateShowDisabledThemes() {
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
+    ]);
     // When there are contributed modules in the site's file system, the
     // total number of attempts made in the test may exceed the default value
     // of update_max_fetch_attempts. Therefore this variable is set very high
     // to avoid test failures in those cases.
     $update_settings->set('fetch.max_attempts', 99999)->save();
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
     $xml_mapping = [
       'drupal' => '0.0',
       'update_test_subtheme' => '1_0',
@@ -426,7 +409,7 @@ public function testUpdateHiddenBaseTheme() {
     \Drupal::service('theme_installer')->install(['update_test_subtheme']);
 
     // Add a project and initial state for base theme and subtheme.
-    $system_info = [
+    $this->mockInstalledExtensionsInfo([
       // Hide the update_test_basetheme.
       'update_test_basetheme' => [
         'project' => 'update_test_basetheme',
@@ -437,8 +420,7 @@ public function testUpdateHiddenBaseTheme() {
         'project' => 'update_test_subtheme',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
     $projects = \Drupal::service('update.manager')->getProjects();
     $theme_data = \Drupal::service('theme_handler')->rebuildThemeData();
     $project_info = new ProjectInfo();
@@ -451,10 +433,7 @@ public function testUpdateHiddenBaseTheme() {
    * Makes sure that if we fetch from a broken URL, sane things happen.
    */
   public function testUpdateBrokenFetchURL() {
-    $system_info = [
-      '#all' => [
-        'version' => '8.0.0',
-      ],
+    $this->mockInstalledExtensionsInfo([
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => '8.x-1.0',
@@ -470,9 +449,8 @@ public function testUpdateBrokenFetchURL() {
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
-
+    ]);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
     // Ensure that the update information is correct before testing.
     $this->drupalGet('admin/reports/updates');
 
@@ -518,24 +496,21 @@ public function testUpdateBrokenFetchURL() {
    * update, then assert if we see the appropriate warnings on the right pages.
    */
   public function testHookUpdateStatusAlter() {
-    $update_test_config = $this->config('update_test.settings');
     $update_admin_user = $this->drupalCreateUser([
       'administer site configuration',
       'administer software updates',
     ]);
     $this->drupalLogin($update_admin_user);
 
-    $system_info = [
-      '#all' => [
-        'version' => '8.0.0',
-      ],
+    $this->mockInstalledExtensionsInfo([
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
-    $update_test_config->set('system_info', $system_info)->save();
+    ]);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
+    $update_test_config = $this->config('update_test.settings');
     $update_status = [
       'aaa_update_test' => [
         'status' => UpdateManagerInterface::NOT_SECURE,
@@ -577,17 +552,14 @@ public function testHookUpdateStatusAlter() {
    * Tests that core compatibility messages are displayed.
    */
   public function testCoreCompatibilityMessage() {
-    $system_info = [
-      '#all' => [
-        'version' => '8.0.0',
-      ],
+    $this->mockInstalledExtensionsInfo([
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
 
     // Confirm that messages are displayed for recommended and latest updates.
     // @todo In https://www.drupal.org/project/drupal/issues/3112962:
@@ -628,17 +600,14 @@ public function testCoreCompatibilityMessage() {
    * @dataProvider securityUpdateAvailabilityProvider
    */
   public function testSecurityUpdateAvailability($module_version, array $expected_security_releases, $expected_update_message_type, $fixture) {
-    $system_info = [
-      '#all' => [
-        'version' => '8.0.0',
-      ],
+    $this->mockInstalledExtensionsInfo([
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => $module_version,
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
     $this->refreshUpdateStatus(['drupal' => '0.0', 'aaa_update_test' => $fixture]);
     $this->assertSecurityUpdates('aaa_update_test', $expected_security_releases, $expected_update_message_type, 'table.update:nth-of-type(2)');
   }
@@ -758,14 +727,13 @@ public function securityUpdateAvailabilityProvider() {
    * release that is published and is the expected update.
    */
   public function testRevokedRelease() {
-    $system_info = [
+    $this->mockInstalledExtensionsInfo([
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
     $this->refreshUpdateStatus([
       'drupal' => '0.0',
       $this->updateProject => '1_0-supported',
@@ -797,14 +765,13 @@ public function testRevokedRelease() {
    * 'supported' and is the expected update.
    */
   public function testUnsupportedRelease() {
-    $system_info = [
+    $this->mockInstalledExtensionsInfo([
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => '8.x-1.1',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
     $this->refreshUpdateStatus([
       'drupal' => '0.0',
       $this->updateProject => '1_0-supported',
@@ -838,16 +805,16 @@ public function testNonStandardVersionStrings() {
       ],
     ];
     foreach ($version_infos as $version_info) {
-      $system_info = [
+      $installed_extensions = [
         'aaa_update_test' => [
           'project' => 'aaa_update_test',
           'hidden' => FALSE,
         ],
       ];
       if (isset($version_info['version'])) {
-        $system_info['aaa_update_test']['version'] = $version_info['version'];
+        $installed_extensions['aaa_update_test']['version'] = $version_info['version'];
       }
-      $this->config('update_test.settings')->set('system_info', $system_info)->save();
+      $this->mockInstalledExtensionsInfo($installed_extensions);
       $this->refreshUpdateStatus([
         'drupal' => '0.0',
         $this->updateProject => '1_0-supported',
diff --git a/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php b/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php
index a0228dadb9c5c576fe51f58f507004cea6fe2196..667015eec92904aee5505faf412f93430a1349b3 100644
--- a/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateManagerUpdateTest.php
@@ -10,6 +10,7 @@
  * @group update
  */
 class UpdateManagerUpdateTest extends UpdateTestBase {
+  use UpdateTestTrait;
 
   /**
    * Modules to enable.
@@ -42,10 +43,7 @@ protected function setUp(): void {
     // The installed state of the system is the same for all test cases. What
     // varies for each test scenario is which release history fixture we fetch,
     // which in turn changes the expected state of the UpdateManagerUpdateForm.
-    $system_info = [
-      '#all' => [
-        'version' => '8.0.0',
-      ],
+    $this->mockInstalledExtensionsInfo([
       'aaa_update_test' => [
         'project' => 'aaa_update_test',
         'version' => '8.x-1.0',
@@ -56,8 +54,8 @@ protected function setUp(): void {
         'version' => '8.x-1.0',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
   }
 
   /**
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverContribTest.php b/core/modules/update/tests/src/Functional/UpdateSemverContribTest.php
index 6d884973084df0aadc6c3a1251319f3457440ac2..213d6e1ce80b537bd7e704863a9f64d398ef9970 100644
--- a/core/modules/update/tests/src/Functional/UpdateSemverContribTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateSemverContribTest.php
@@ -8,6 +8,7 @@
  * @group update
  */
 class UpdateSemverContribTest extends UpdateSemverTestBase {
+  use UpdateTestTrait;
 
   /**
    * {@inheritdoc}
@@ -33,7 +34,7 @@ class UpdateSemverContribTest extends UpdateSemverTestBase {
    * {@inheritdoc}
    */
   protected function setProjectInstalledVersion($version) {
-    $system_info = [
+    $this->mockInstalledExtensionsInfo([
       $this->updateProject => [
         'project' => $this->updateProject,
         'version' => $version,
@@ -45,8 +46,8 @@ protected function setProjectInstalledVersion($version) {
         'version' => '8.0.0',
         'hidden' => FALSE,
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
   }
 
 }
diff --git a/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php b/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php
index 829b68dd7b308d8ef86bdc150eab855edb01cff7..cd87ee90bc80bdbd11473c9acc859ea3a582ac36 100644
--- a/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateSemverCoreTest.php
@@ -10,6 +10,7 @@
  * @group update
  */
 class UpdateSemverCoreTest extends UpdateSemverTestBase {
+  use UpdateTestTrait;
 
   /**
    * {@inheritdoc}
@@ -33,12 +34,7 @@ class UpdateSemverCoreTest extends UpdateSemverTestBase {
    *   The version.
    */
   protected function setProjectInstalledVersion($version) {
-    $setting = [
-      '#all' => [
-        'version' => $version,
-      ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $setting)->save();
+    $this->mockDefaultExtensionsInfo(['version' => $version]);
   }
 
   /**
@@ -276,18 +272,17 @@ public function securityCoverageMessageProvider() {
    * Ensures proper results where there are date mismatches among modules.
    */
   public function testDatestampMismatch() {
-    $system_info = [
-      '#all' => [
-        // We need to think we're running a -dev snapshot to see dates.
-        'version' => '8.1.0-dev',
-        'datestamp' => time(),
-      ],
+    $this->mockInstalledExtensionsInfo([
       'block' => [
         // This is 2001-09-09 01:46:40 GMT, so test for "2001-Sep-".
         'datestamp' => '1000000000',
       ],
-    ];
-    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+    ]);
+    // We need to think we're running a -dev snapshot to see dates.
+    $this->mockDefaultExtensionsInfo([
+      'version' => '8.1.0-dev',
+      'datestamp' => time(),
+    ]);
     $this->refreshUpdateStatus(['drupal' => 'dev']);
     $this->assertSession()->pageTextNotContains('2001-Sep-');
     $this->assertSession()->pageTextContains('Up to date');
@@ -303,9 +298,7 @@ public function testModulePageRunCron() {
     $this->config('update.settings')
       ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
       ->save();
-    $this->config('update_test.settings')
-      ->set('xml_map', ['drupal' => '0.0'])
-      ->save();
+    $this->mockReleaseHistory(['drupal' => '0.0']);
 
     $this->cronRun();
     $this->drupalGet('admin/modules');
@@ -338,9 +331,7 @@ public function testModulePageUpToDate() {
     $this->config('update.settings')
       ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
       ->save();
-    $this->config('update_test.settings')
-      ->set('xml_map', ['drupal' => '0.0'])
-      ->save();
+    $this->mockReleaseHistory(['drupal' => '0.0']);
 
     $this->drupalGet('admin/reports/updates');
     $this->clickLink('Check manually');
@@ -365,9 +356,7 @@ public function testModulePageRegularUpdate() {
     $this->config('update.settings')
       ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
       ->save();
-    $this->config('update_test.settings')
-      ->set('xml_map', ['drupal' => '0.1'])
-      ->save();
+    $this->mockReleaseHistory(['drupal' => '0.1']);
 
     $this->drupalGet('admin/reports/updates');
     $this->clickLink('Check manually');
@@ -403,9 +392,7 @@ public function testModulePageSecurityUpdate() {
     $this->config('update.settings')
       ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
       ->save();
-    $this->config('update_test.settings')
-      ->set('xml_map', ['drupal' => 'sec.0.2'])
-      ->save();
+    $this->mockReleaseHistory(['drupal' => 'sec.0.2']);
 
     $this->drupalGet('admin/reports/updates');
     $this->clickLink('Check manually');
@@ -476,9 +463,7 @@ public function testLanguageModuleUpdate() {
     $this->config('update.settings')
       ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
       ->save();
-    $this->config('update_test.settings')
-      ->set('xml_map', ['drupal' => '0.1'])
-      ->save();
+    $this->mockReleaseHistory(['drupal' => '0.1']);
 
     $this->drupalGet('admin/reports/updates');
     $this->assertSession()->pageTextContains('Language');
@@ -525,18 +510,14 @@ public function testBrokenThenFixedUpdates() {
       ->save();
     // Use update XML that has no information to simulate a broken response from
     // the update server.
-    $this->config('update_test.settings')
-      ->set('xml_map', ['drupal' => 'broken'])
-      ->save();
+    $this->mockReleaseHistory(['drupal' => 'broken']);
 
     // This will retrieve broken updates.
     $this->cronRun();
     $this->drupalGet('admin/reports/status');
     $this->assertSession()->statusCodeEquals(200);
     $this->assertSession()->pageTextContains('There was a problem checking available updates for Drupal.');
-    $this->config('update_test.settings')
-      ->set('xml_map', ['drupal' => 'sec.0.2'])
-      ->save();
+    $this->mockReleaseHistory(['drupal' => 'sec.0.2']);
     // Simulate the update_available_releases state expiring before cron is run
     // and the state is used by \Drupal\update\UpdateManager::getProjects().
     \Drupal::keyValueExpirable('update_available_releases')->deleteAll();
diff --git a/core/modules/update/tests/src/Functional/UpdateTestBase.php b/core/modules/update/tests/src/Functional/UpdateTestBase.php
index c0e57cee38ed29851411589b82b7d043cce9518c..755b11c00fc3b1b5030c00135cbbf908db203add 100644
--- a/core/modules/update/tests/src/Functional/UpdateTestBase.php
+++ b/core/modules/update/tests/src/Functional/UpdateTestBase.php
@@ -23,6 +23,7 @@
  * initial state and availability scenario.
  */
 abstract class UpdateTestBase extends BrowserTestBase {
+  use UpdateTestTrait;
 
   /**
    * Denotes a security update will be required in the test case.
@@ -70,7 +71,7 @@ protected function refreshUpdateStatus($xml_map, $url = 'update-test') {
     // update_test module.
     $this->config('update.settings')->set('fetch.url', Url::fromUri('base:' . $url, ['absolute' => TRUE])->toString())->save();
     // Save the map for UpdateTestController::updateTest() to use.
-    $this->config('update_test.settings')->set('xml_map', $xml_map)->save();
+    $this->mockReleaseHistory($xml_map);
     // Manually check the update status.
     $this->drupalGet('admin/reports/updates');
     $this->clickLink('Check manually');
diff --git a/core/modules/update/tests/src/Functional/UpdateTestTrait.php b/core/modules/update/tests/src/Functional/UpdateTestTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..f92b03c2180ac7e4913836192f6741e322e15dc1
--- /dev/null
+++ b/core/modules/update/tests/src/Functional/UpdateTestTrait.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\Tests\update\Functional;
+
+/**
+ * Provides a trait to set system info and XML mappings.
+ *
+ * @see update_test_system_info_alter
+ * @see \Drupal\update_test\Controller\UpdateTestController::updateTest
+ * @see \Drupal\Core\Extension\ExtensionList::doList()
+ * @see \Drupal\Core\Extension\InfoParserInterface
+ * @see update_test_system_info_alter
+ */
+trait UpdateTestTrait {
+
+  /**
+   * Sets information about installed extensions.
+   *
+   * @param string[][] $installed_extensions
+   *   An array containing mocked installed extensions info. Keys are
+   *   extension names, values are arrays containing key-value pairs that would
+   *   be present in extensions' *.info.yml files.
+   *   For a list of accepted keys, see InfoParserInterface. Key-value pairs not
+   *   present here will be inherited from $default_info.
+   *   For example:
+   *
+   * @code
+   *   'drupal' => [
+   *     'project' => 'drupal',
+   *     'version' => '8.0.0',
+   *     'hidden' => FALSE,
+   *   ]
+   * @endcode
+   *
+   * @throws \Exception
+   */
+  protected function mockInstalledExtensionsInfo(array $installed_extensions): void {
+    if (in_array('#all', array_keys($installed_extensions), TRUE)) {
+      throw new \Exception("#all (default value) shouldn't be set here instead use ::mockDefaultExtensionsInfo().");
+    }
+    $system_info = $this->config('update_test.settings')->get('system_info');
+    $system_info = $installed_extensions + $system_info;
+    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+  }
+
+  /**
+   * Sets default information about installed extensions.
+   *
+   * @param string[] $default_info
+   *   The *.info.yml key-value pairs to be mocked across all
+   *   extensions. Hence, these can be seen as default/fallback values.
+   */
+  protected function mockDefaultExtensionsInfo(array $default_info): void {
+    $system_info = $this->config('update_test.settings')->get('system_info');
+    $system_info = ['#all' => $default_info] + $system_info;
+    $this->config('update_test.settings')->set('system_info', $system_info)->save();
+  }
+
+  /**
+   * Sets available release history.
+   *
+   * @param string[] $release_history
+   *   The release history XML files to use for particular extension(s). The
+   *   keys are the extension names (use 'drupal' for Drupal core itself), and
+   *   the values are the suffix of the release history XML file to use. For
+   *   example, @code 'drupal' => 'sec.0.2' @endcode will map to a file called
+   *   drupal.sec.0.2.xml. Look at
+   *   core/modules/update/tests/fixtures/release-history for more release
+   *   history XML examples.
+   */
+  protected function mockReleaseHistory(array $release_history): void {
+    $this->config('update_test.settings')->set('xml_map', $release_history)->save();
+  }
+
+}
diff --git a/core/modules/update/tests/src/Functional/UpdateUploadTest.php b/core/modules/update/tests/src/Functional/UpdateUploadTest.php
index fd2643b273369e6f02e05e3ed2d4a5db34a13160..7c60ab7598c179a9dfaf687bcb876f4468569e01 100644
--- a/core/modules/update/tests/src/Functional/UpdateUploadTest.php
+++ b/core/modules/update/tests/src/Functional/UpdateUploadTest.php
@@ -124,13 +124,11 @@ public function testUploadModule() {
 
     // Define the update XML such that the new module downloaded above needs an
     // update from 8.x-1.0 to 8.x-1.1.
-    $update_test_config = $this->config('update_test.settings');
-    $system_info = [
+    $this->mockInstalledExtensionsInfo([
       'update_test_new_module' => [
         'project' => 'update_test_new_module',
       ],
-    ];
-    $update_test_config->set('system_info', $system_info)->save();
+    ]);
     $xml_mapping = [
       'update_test_new_module' => '1_1',
     ];
@@ -164,15 +162,8 @@ public function testFileNameExtensionMerging() {
    * Checks the messages on update manager pages when missing a security update.
    */
   public function testUpdateManagerCoreSecurityUpdateMessages() {
-    $setting = [
-      '#all' => [
-        'version' => '8.0.0',
-      ],
-    ];
-    $this->config('update_test.settings')
-      ->set('system_info', $setting)
-      ->set('xml_map', ['drupal' => '0.2-sec'])
-      ->save();
+    $this->mockDefaultExtensionsInfo(['version' => '8.0.0']);
+    $this->mockReleaseHistory(['drupal' => '0.2-sec']);
     $this->config('update.settings')
       ->set('fetch.url', Url::fromRoute('update_test.update_test')->setAbsolute()->toString())
       ->save();