diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index c46f9109e7fd617d56fdbf5355fe8dc6d1c6bf45..31b686bec0f7b79c7b64eea173ff06f549780662 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -495,6 +495,7 @@ protected function processExtension(array &$cache, $name, $type, $theme, $path)
         // if the theme hook specifies a function callback instead, check to
         // ensure the function actually exists.
         if (isset($info['function'])) {
+          @trigger_error('Theme functions are deprecated in drupal:8.0.0 and are removed from drupal:10.0.0. Use Twig templates instead. See https://www.drupal.org/node/1831138', E_USER_DEPRECATED);
           if (!function_exists($info['function'])) {
             throw new \BadFunctionCallException(sprintf(
               'Theme hook "%s" refers to a theme function callback that does not exist: "%s"',
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index 2f94691f2752ed90f2a0bce92a9663eca11ed045..45e59530b972044d1f815e7f61920a941a43e205 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -121,27 +121,9 @@ function common_test_theme() {
     'common_test_render_element' => [
       'render element' => 'foo',
     ],
-    'common_test_empty' => [
-      'variables' => ['foo' => 'foo'],
-      'function' => 'theme_common_test_empty',
-    ],
   ];
 }
 
-/**
- * Provides a theme function for drupal_render().
- */
-function theme_common_test_foo($variables) {
-  return $variables['foo'] . $variables['bar'];
-}
-
-/**
- * Always returns an empty string.
- */
-function theme_common_test_empty($variables) {
-  return '';
-}
-
 /**
  * Implements MODULE_preprocess().
  *
diff --git a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.inc b/core/modules/system/tests/modules/theme_legacy_suggestions_test/theme_legacy_suggestions_test.inc
similarity index 74%
rename from core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.inc
rename to core/modules/system/tests/modules/theme_legacy_suggestions_test/theme_legacy_suggestions_test.inc
index efc0144551a12721e2fd73951bcf8104d73eee20..fc977ae7e3e06b21e7486132789cda09d4239eb4 100644
--- a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.inc
+++ b/core/modules/system/tests/modules/theme_legacy_suggestions_test/theme_legacy_suggestions_test.inc
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Include file for testing theme suggestion hooks.
+ * Include file for testing theme suggestion hooks for legacy theme functions.
  */
 
 /**
diff --git a/core/modules/system/tests/modules/theme_legacy_suggestions_test/theme_legacy_suggestions_test.info.yml b/core/modules/system/tests/modules/theme_legacy_suggestions_test/theme_legacy_suggestions_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cf1f0aec7753df342bcb1365c4af39d82f78253f
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_legacy_suggestions_test/theme_legacy_suggestions_test.info.yml
@@ -0,0 +1,5 @@
+name: 'Legacy theme functions suggestions test'
+type: module
+description: 'Support module for testing theme suggestions for legacy theme functions.'
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/theme_legacy_suggestions_test/theme_legacy_suggestions_test.module b/core/modules/system/tests/modules/theme_legacy_suggestions_test/theme_legacy_suggestions_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..3986663b90c611ebdb9edaf9452395cde2ca5eeb
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_legacy_suggestions_test/theme_legacy_suggestions_test.module
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Support module for testing theme suggestions.
+ *
+ * @todo Remove in https://www.drupal.org/project/drupal/issues/3097889
+ */
+
+/**
+ * Implements hook_theme().
+ */
+function theme_legacy_suggestions_test_theme() {
+  $items['theme_suggestions_test_include'] = [
+    'file' => 'theme_legacy_suggestions_test.inc',
+    'function' => 'theme_theme_suggestions_test_include',
+  ];
+  return $items;
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function theme_legacy_suggestions_test_theme_suggestions_theme_test_function_suggestions_alter(array &$suggestions, array $variables) {
+  $suggestions[] = 'theme_test_function_suggestions__module_override';
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function theme_legacy_suggestions_test_theme_suggestions_theme_test_suggestions_include_alter(array &$suggestions, array $variables, $hook) {
+  $suggestions[] = 'theme_suggestions_test_include';
+}
diff --git a/core/modules/system/tests/modules/theme_legacy_test/src/ThemeTestController.php b/core/modules/system/tests/modules/theme_legacy_test/src/ThemeTestController.php
new file mode 100644
index 0000000000000000000000000000000000000000..843858cc5f9ca38b77385e33ad3fb7b479b7e4a9
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_legacy_test/src/ThemeTestController.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\theme_legacy_test;
+
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Controller routines for test routes for legacy theme functions.
+ *
+ * @todo Remove in https://www.drupal.org/project/drupal/issues/3097889
+ */
+class ThemeTestController extends ControllerBase {
+
+  /**
+   * A theme template that overrides a theme function.
+   *
+   * @return array
+   *   Render array containing a theme.
+   */
+  public function functionTemplateOverridden() {
+    return [
+      '#theme' => 'theme_test_function_template_override',
+    ];
+  }
+
+  /**
+   * Menu callback for testing suggestion alter hooks with theme functions.
+   */
+  public function functionSuggestionAlter() {
+    return ['#theme' => 'theme_test_function_suggestions'];
+  }
+
+  /**
+   * Menu callback for testing includes with suggestion alter hooks.
+   */
+  public function suggestionAlterInclude() {
+    return ['#theme' => 'theme_test_suggestions_include'];
+  }
+
+}
diff --git a/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.inc b/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.inc
new file mode 100644
index 0000000000000000000000000000000000000000..bbf74ffb74dc31a6b8b6f1302071a5e3a23260f0
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.inc
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Include file for testing legacy theme functions.
+ */
+
+/**
+ * Returns HTML for the 'theme_test' theme hook used by tests.
+ */
+function theme_theme_test($variables) {
+  return 'Theme hook implementor=theme_theme_test(). Foo=' . $variables['foo'];
+}
diff --git a/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.info.yml b/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dd537a0a9ec0abcc8e845b75f1dd84a5ea6c8be3
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.info.yml
@@ -0,0 +1,5 @@
+name: 'Legacy theme functions test'
+type: module
+description: 'Support module for testing legacy theme functions.'
+package: Testing
+version: VERSION
diff --git a/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.module b/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..6001e4fd00e504e8fa7acb5ca7afb1ca1b6dc092
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.module
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * @file
+ * Test module for legacy theme functions.
+ *
+ * @todo Remove in https://www.drupal.org/project/drupal/issues/3097889
+ */
+
+/**
+ * Implements hook_theme().
+ */
+function theme_legacy_test_theme($existing, $type, $theme, $path) {
+  $items['theme_test'] = [
+    'file' => 'theme_legacy_test.inc',
+    'variables' => ['foo' => ''],
+    'function' => 'theme_theme_test',
+  ];
+  $items['theme_test_function_suggestions'] = [
+    'variables' => [],
+    'function' => 'theme_theme_test_function_suggestions',
+  ];
+  $items['theme_test_suggestions_include'] = [
+    'variables' => [],
+    'function' => 'theme_theme_test_suggestions_include',
+  ];
+  $items['theme_test_foo'] = [
+    'variables' => ['foo' => NULL],
+    'function' => 'theme_theme_test_foo',
+  ];
+  $items['theme_test_render_element_children'] = [
+    'render element' => 'element',
+    'function' => 'theme_theme_test_render_element_children',
+  ];
+  $items['theme_test_function_template_override'] = [
+    'variables' => [],
+    'function' => 'theme_theme_test_function_template_override',
+  ];
+  $info['test_theme_not_existing_function'] = [
+    'function' => 'test_theme_not_existing_function',
+  ];
+  return $items;
+}
+
+/**
+ * Implements template_preprocess_HOOK() for theme_test_function_suggestions theme functions.
+ */
+function template_preprocess_theme_test_function_suggestions(&$variables) {
+}
+
+/**
+ * Theme function for hook theme_test_foo.
+ */
+function theme_theme_test_foo($variables) {
+  return $variables['foo'];
+}
+
+/**
+ * Theme function for hook theme_test_function_template_override.
+ */
+function theme_theme_test_function_template_override($variables) {
+  return 'theme_test_function_template_override test failed.';
+}
+
+/**
+ * Theme function for testing rendering of child elements via drupal_render().
+ *
+ * Theme hooks defining a 'render element' add an internal '#render_children'
+ * property. When this property is found, drupal_render() avoids calling
+ * the 'theme.manager' service 'render' method on the top-level element to
+ * prevent infinite recursion.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ */
+function theme_theme_test_render_element_children($variables) {
+  return \Drupal::service('renderer')->render($variables['element']);
+}
+
+/**
+ * Returns HTML for a theme function suggestion test.
+ */
+function theme_theme_test_function_suggestions($variables) {
+  return 'Original theme function.';
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK().
+ */
+function theme_legacy_test_theme_suggestions_theme_test_suggestion_provided(array $variables) {
+  return ['theme_test_suggestion_provided__foo'];
+}
+
+/**
+ * Implements hook_theme_suggestions_alter().
+ */
+function theme_legacy_test_theme_suggestions_alter(array &$suggestions, array $variables, $hook) {
+  \Drupal::messenger()
+    ->addStatus(__FUNCTION__ . '() executed for ' . $hook . '.');
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function theme_legacy_test_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) {
+  \Drupal::messenger()->addStatus(__FUNCTION__ . '() executed.');
+}
+
+/**
+ * Returns HTML for a theme function include test.
+ */
+function theme_theme_test_suggestions_include($variables) {
+  return 'Original function before altering theme suggestions.';
+}
diff --git a/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.routing.yml b/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.routing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ffc6b45776c13aba4d7f3c3ceb3eab812eae85aa
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.routing.yml
@@ -0,0 +1,22 @@
+theme_test.function_template_override:
+  path: '/theme-test/function-template-overridden'
+  options:
+    _custom_theme: 'test_theme'
+  defaults:
+    _controller: '\Drupal\theme_legacy_test\ThemeTestController::functionTemplateOverridden'
+  requirements:
+    _access: 'TRUE'
+
+function_suggestion_alter:
+  path: '/theme-test/function-suggestion-alter'
+  defaults:
+    _controller: '\Drupal\theme_legacy_test\ThemeTestController::functionSuggestionAlter'
+  requirements:
+    _access: 'TRUE'
+
+suggestion_alter_include:
+  path: '/theme-test/suggestion-alter-include'
+  defaults:
+    _controller: '\Drupal\theme_legacy_test\ThemeTestController::suggestionAlterInclude'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
index 5d4420270a8ade3ee20c47e46ba74163a96767a6..b6d2f80cf2a5f1465658fc131a3dd102788f8d38 100644
--- a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
+++ b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
@@ -5,18 +5,6 @@
  * Support module for testing theme suggestions.
  */
 
-/**
- * Implements hook_theme().
- */
-function theme_suggestions_test_theme() {
-  $items['theme_suggestions_test_include'] = [
-    'file' => 'theme_suggestions_test.inc',
-    'variables' => [],
-    'function' => 'theme_theme_suggestions_test_include',
-  ];
-  return $items;
-}
-
 /**
  * Implements hook_theme_suggestions_alter().
  */
@@ -35,23 +23,9 @@ function theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter(a
   $suggestions[] = 'theme_test_suggestions__module_override';
 }
 
-/**
- * Implements hook_theme_suggestions_HOOK_alter().
- */
-function theme_suggestions_test_theme_suggestions_theme_test_function_suggestions_alter(array &$suggestions, array $variables) {
-  $suggestions[] = 'theme_test_function_suggestions__module_override';
-}
-
 /**
  * Implements hook_theme_suggestions_HOOK_alter().
  */
 function theme_suggestions_test_theme_suggestions_theme_test_specific_suggestions_alter(array &$suggestions, array $variables) {
   $suggestions[] = 'theme_test_specific_suggestions__variant__foo';
 }
-
-/**
- * Implements hook_theme_suggestions_HOOK_alter().
- */
-function theme_suggestions_test_theme_suggestions_theme_test_suggestions_include_alter(array &$suggestions, array $variables, $hook) {
-  $suggestions[] = 'theme_suggestions_test_include';
-}
diff --git a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
index bd46aeaed9007a3d0af1091a0ee81d47cbc16bbd..2da8e8022dc51c36c3a1de0ba8ae37350464d4aa 100644
--- a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
+++ b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
@@ -10,18 +10,6 @@
  */
 class ThemeTestController extends ControllerBase {
 
-  /**
-   * A theme template that overrides a theme function.
-   *
-   * @return array
-   *   Render array containing a theme.
-   */
-  public function functionTemplateOverridden() {
-    return [
-      '#theme' => 'theme_test_function_template_override',
-    ];
-  }
-
   /**
    * Adds stylesheets to test theme .info.yml property processing.
    *
@@ -112,20 +100,6 @@ public function specificSuggestionAlter() {
     return ['#theme' => 'theme_test_specific_suggestions__variant'];
   }
 
-  /**
-   * Menu callback for testing suggestion alter hooks with theme functions.
-   */
-  public function functionSuggestionAlter() {
-    return ['#theme' => 'theme_test_function_suggestions'];
-  }
-
-  /**
-   * Menu callback for testing includes with suggestion alter hooks.
-   */
-  public function suggestionAlterInclude() {
-    return ['#theme' => 'theme_test_suggestions_include'];
-  }
-
   /**
    * Controller to ensure that no theme is initialized.
    *
diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-foo.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-foo.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..cd5a26500344e98dfc63415f4499742d1e3836d8
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-foo.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+{{ foo -}}
diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-render-element-children.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-render-element-children.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..6a9d0fdf25ffba543da297906afd34bdcf8d7cc8
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-render-element-children.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+{{ element -}}
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.inc b/core/modules/system/tests/modules/theme_test/theme_test.inc
index 1e0a7a654ad8c4022a801d4fee559ff24356539b..050d3e98a0bba84ee2943bb06fe3d0807d9fc6b8 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.inc
+++ b/core/modules/system/tests/modules/theme_test/theme_test.inc
@@ -5,13 +5,6 @@
  * Include file for test module.
  */
 
-/**
- * Returns HTML for the 'theme_test' theme hook used by tests.
- */
-function theme_theme_test($variables) {
-  return 'Theme hook implementor=theme_theme_test(). Foo=' . $variables['foo'];
-}
-
 /**
  * Preprocesses variables for theme_theme_test().
  */
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index 113676cbe9e5fe18f8a8e6d43f348f4daed141fc..3763c3a18ad0233dd7be41b79efe31362e98abba 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -14,7 +14,6 @@ function theme_test_theme($existing, $type, $theme, $path) {
   $items['theme_test'] = [
     'file' => 'theme_test.inc',
     'variables' => ['foo' => ''],
-    'function' => 'theme_theme_test',
   ];
   $items['theme_test_template_test'] = [
     'template' => 'theme_test.template_test',
@@ -34,31 +33,14 @@ function theme_test_theme($existing, $type, $theme, $path) {
   $items['theme_test_general_suggestions'] = [
     'variables' => [],
   ];
-  $items['theme_test_function_suggestions'] = [
-    'variables' => [],
-    'function' => 'theme_theme_test_function_suggestions',
-  ];
-  $items['theme_test_suggestions_include'] = [
-    'variables' => [],
-    'function' => 'theme_theme_test_suggestions_include',
-  ];
   $items['theme_test_foo'] = [
     'variables' => ['foo' => NULL],
-    'function' => 'theme_theme_test_foo',
   ];
   $items['theme_test_render_element'] = [
     'render element' => 'elements',
   ];
   $items['theme_test_render_element_children'] = [
     'render element' => 'element',
-    'function' => 'theme_theme_test_render_element_children',
-  ];
-  $items['theme_test_function_template_override'] = [
-    'variables' => [],
-    'function' => 'theme_theme_test_function_template_override',
-  ];
-  $info['test_theme_not_existing_function'] = [
-    'function' => 'test_theme_not_existing_function',
   ];
   $items['theme_test_preprocess_suggestions'] = [
     'variables' => [
@@ -95,26 +77,6 @@ function theme_test_page_bottom(array &$page_bottom) {
   $page_bottom['theme_test_page_bottom'] = ['#markup' => 'theme test page bottom markup'];
 }
 
-/**
- * Implements template_preprocess_HOOK() for theme_test_function_suggestions theme functions.
- */
-function template_preprocess_theme_test_function_suggestions(&$variables) {
-}
-
-/**
- * Theme function for hook theme_test_foo.
- */
-function theme_theme_test_foo($variables) {
-  return $variables['foo'];
-}
-
-/**
- * Theme function for hook theme_test_function_template_override.
- */
-function theme_theme_test_function_template_override($variables) {
-  return 'theme_test_function_template_override test failed.';
-}
-
 /**
  * Implements hook_theme_suggestions_HOOK().
  */
@@ -149,29 +111,6 @@ function template_preprocess_theme_test_render_element(&$variables) {
   $variables['attributes']['data-variables-are-preprocessed'] = TRUE;
 }
 
-/**
- * Theme function for testing rendering of child elements via drupal_render().
- *
- * Theme hooks defining a 'render element' add an internal '#render_children'
- * property. When this property is found, drupal_render() avoids calling
- * the 'theme.manager' service 'render' method on the top-level element to
- * prevent infinite recursion.
- *
- * @param array $variables
- *   An associative array containing:
- *   - element: An associative array containing the properties of the element.
- */
-function theme_theme_test_render_element_children($variables) {
-  return \Drupal::service('renderer')->render($variables['element']);
-}
-
-/**
- * Returns HTML for a theme function suggestion test.
- */
-function theme_theme_test_function_suggestions($variables) {
-  return 'Original theme function.';
-}
-
 /**
  * Implements hook_theme_suggestions_HOOK().
  */
@@ -193,13 +132,6 @@ function theme_test_theme_suggestions_theme_test_suggestions_alter(array &$sugge
   \Drupal::messenger()->addStatus(__FUNCTION__ . '() executed.');
 }
 
-/**
- * Returns HTML for a theme function include test.
- */
-function theme_theme_test_suggestions_include($variables) {
-  return 'Original function before altering theme suggestions.';
-}
-
 /**
  * Implements hook_system_info_alter().
  *
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
index 6f880918f2fd8d72673441ddcd9190ff55edc719..1c12fef19953ddf8dd7dc987c38d2109b240513a 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
+++ b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
@@ -1,12 +1,3 @@
-theme_test.function_template_override:
-  path: '/theme-test/function-template-overridden'
-  options:
-    _custom_theme: 'test_theme'
-  defaults:
-    _controller: '\Drupal\theme_test\ThemeTestController::functionTemplateOverridden'
-  requirements:
-    _access: 'TRUE'
-
 theme_test.info_stylesheets:
   path: '/theme-test/info/stylesheets'
   defaults:
@@ -83,20 +74,6 @@ specific_suggestion_alter:
   requirements:
     _access: 'TRUE'
 
-function_suggestion_alter:
-  path: '/theme-test/function-suggestion-alter'
-  defaults:
-    _controller: '\Drupal\theme_test\ThemeTestController::functionSuggestionAlter'
-  requirements:
-    _access: 'TRUE'
-
-suggestion_alter_include:
-  path: '/theme-test/suggestion-alter-include'
-  defaults:
-    _controller: '\Drupal\theme_test\ThemeTestController::suggestionAlterInclude'
-  requirements:
-    _access: 'TRUE'
-
 theme_test.non_html:
   path: '/theme-test/non-html'
   defaults:
diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeLegacyTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeLegacyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..51de2eae46176bcf0168db720520f81c6ce7fa06
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Theme/ThemeLegacyTest.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Theme;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests legacy theme functions.
+ *
+ * @group Theme
+ * @group legacy
+ *
+ * @todo Remove in https://www.drupal.org/project/drupal/issues/3097889
+ */
+class ThemeLegacyTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['theme_test', 'theme_legacy_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  protected function setUp() {
+    parent::setUp();
+    \Drupal::service('theme_installer')->install(['test_legacy_theme']);
+  }
+
+  /**
+   * Ensures a theme template can override a theme function.
+   */
+  public function testFunctionOverride() {
+    $this->drupalGet('theme-test/function-template-overridden');
+    $this->assertText('Success: Template overrides theme function.', 'Theme function overridden by test_theme template.');
+  }
+
+  /**
+   * Tests that theme suggestion alter hooks work for theme functions.
+   */
+  public function testThemeFunctionSuggestionsAlter() {
+    $this->drupalGet('theme-test/function-suggestion-alter');
+    $this->assertText('Original theme function.');
+
+    // Install test_theme and test that themes can alter theme suggestions.
+    $this->config('system.theme')
+      ->set('default', 'test_legacy_theme')
+      ->save();
+    $this->drupalGet('theme-test/function-suggestion-alter');
+    $this->assertText('Theme function overridden based on new theme suggestion provided by the test_legacy_theme theme.');
+
+    // Enable the theme_suggestions_test module to test modules implementing
+    // suggestions alter hooks.
+    \Drupal::service('module_installer')->install(['theme_legacy_suggestions_test']);
+    $this->resetAll();
+    $this->drupalGet('theme-test/function-suggestion-alter');
+    $this->assertText('Theme function overridden based on new theme suggestion provided by a module.');
+  }
+
+  /**
+   * Tests that theme suggestion alter hooks work with theme hook includes.
+   */
+  public function testSuggestionsAlterInclude() {
+    // Check the original theme output.
+    $this->drupalGet('theme-test/suggestion-alter-include');
+    $this->assertText('Original function before altering theme suggestions.');
+
+    // Enable theme_suggestions_test module and make two requests to make sure
+    // the include file is always loaded. The file will always be included for
+    // the first request because the theme registry is being rebuilt.
+    \Drupal::service('module_installer')->install(['theme_legacy_suggestions_test']);
+    $this->resetAll();
+    $this->drupalGet('theme-test/suggestion-alter-include');
+    $this->assertText('Function suggested via suggestion alter hook found in include file.', 'Include file loaded for initial request.');
+    $this->drupalGet('theme-test/suggestion-alter-include');
+    $this->assertText('Function suggested via suggestion alter hook found in include file.', 'Include file loaded for second request.');
+  }
+
+}
diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php
index c521f58e50f79efe595f4a534259beae115f035e..dcfdf150865438063e4fc647b0acfaf4d92b1575 100644
--- a/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php
+++ b/core/modules/system/tests/src/Functional/Theme/ThemeSuggestionsAlterTest.php
@@ -116,47 +116,6 @@ public function testSpecificSuggestionsAlter() {
     $this->assertTrue(strpos($raw_content, 'theme_test_specific_suggestions__variant') < strpos($raw_content, 'theme_test_specific_suggestions__variant__foo'), 'Specific theme call is added to the suggestions array before the suggestions alter hook.');
   }
 
-  /**
-   * Tests that theme suggestion alter hooks work for theme functions.
-   */
-  public function testThemeFunctionSuggestionsAlter() {
-    $this->drupalGet('theme-test/function-suggestion-alter');
-    $this->assertText('Original theme function.');
-
-    // Install test_theme and test that themes can alter theme suggestions.
-    $this->config('system.theme')
-      ->set('default', 'test_theme')
-      ->save();
-    $this->drupalGet('theme-test/function-suggestion-alter');
-    $this->assertText('Theme function overridden based on new theme suggestion provided by the test_theme theme.');
-
-    // Enable the theme_suggestions_test module to test modules implementing
-    // suggestions alter hooks.
-    \Drupal::service('module_installer')->install(['theme_suggestions_test']);
-    $this->resetAll();
-    $this->drupalGet('theme-test/function-suggestion-alter');
-    $this->assertText('Theme function overridden based on new theme suggestion provided by a module.');
-  }
-
-  /**
-   * Tests that theme suggestion alter hooks work with theme hook includes.
-   */
-  public function testSuggestionsAlterInclude() {
-    // Check the original theme output.
-    $this->drupalGet('theme-test/suggestion-alter-include');
-    $this->assertText('Original function before altering theme suggestions.');
-
-    // Enable theme_suggestions_test module and make two requests to make sure
-    // the include file is always loaded. The file will always be included for
-    // the first request because the theme registry is being rebuilt.
-    \Drupal::service('module_installer')->install(['theme_suggestions_test']);
-    $this->resetAll();
-    $this->drupalGet('theme-test/suggestion-alter-include');
-    $this->assertText('Function suggested via suggestion alter hook found in include file.', 'Include file loaded for initial request.');
-    $this->drupalGet('theme-test/suggestion-alter-include');
-    $this->assertText('Function suggested via suggestion alter hook found in include file.', 'Include file loaded for second request.');
-  }
-
   /**
    * Tests execution order of theme suggestion alter hooks.
    *
diff --git a/core/modules/system/tests/src/Functional/Theme/ThemeTest.php b/core/modules/system/tests/src/Functional/Theme/ThemeTest.php
index fe4471a2fa28fc9514dbec7e62751fd2d8fafa6c..8058a752d28ee4e8216982073fb63de338219b98 100644
--- a/core/modules/system/tests/src/Functional/Theme/ThemeTest.php
+++ b/core/modules/system/tests/src/Functional/Theme/ThemeTest.php
@@ -45,7 +45,7 @@ public function testPreprocessForSuggestions() {
     drupal_theme_rebuild();
     for ($i = 0; $i < 2; $i++) {
       $this->drupalGet('theme-test/suggestion');
-      $this->assertText('Theme hook implementor=test_theme_theme_test__suggestion(). Foo=template_preprocess_theme_test', 'Theme hook suggestion ran with data available from a preprocess function for the base hook.');
+      $this->assertText('Theme hook implementor=theme-test--suggestion.html.twig. Foo=template_preprocess_theme_test', 'Theme hook suggestion ran with data available from a preprocess function for the base hook.');
     }
   }
 
@@ -56,7 +56,7 @@ public function testNegotiatorPriorities() {
     $this->drupalGet('theme-test/priority');
 
     // Ensure that the custom theme negotiator was not able to set the theme.
-    $this->assertNoText('Theme hook implementor=test_theme_theme_test__suggestion(). Foo=template_preprocess_theme_test', 'Theme hook suggestion ran with data available from a preprocess function for the base hook.');
+    $this->assertNoText('Theme hook implementor=theme-test--suggestion.html.twig. Foo=template_preprocess_theme_test', 'Theme hook suggestion ran with data available from a preprocess function for the base hook.');
   }
 
   /**
@@ -141,14 +141,6 @@ public function testTemplateOverride() {
     $this->assertText('Success: Template overridden.', 'Template overridden by defined \'template\' filename.');
   }
 
-  /**
-   * Ensures a theme template can override a theme function.
-   */
-  public function testFunctionOverride() {
-    $this->drupalGet('theme-test/function-template-overridden');
-    $this->assertText('Success: Template overrides theme function.', 'Theme function overridden by test_theme template.');
-  }
-
   /**
    * Tests that the page variable is not prematurely flattened.
    *
diff --git a/core/modules/system/tests/themes/test_legacy_theme/test_legacy_theme.info.yml b/core/modules/system/tests/themes/test_legacy_theme/test_legacy_theme.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d49d40105fd901119229fe3a13d782a3a8bde62d
--- /dev/null
+++ b/core/modules/system/tests/themes/test_legacy_theme/test_legacy_theme.info.yml
@@ -0,0 +1,6 @@
+name: 'Test legacy theme'
+type: theme
+description: 'Test theme to test deprecated functionality.'
+version: VERSION
+core: 8.x
+base theme: test_theme
diff --git a/core/modules/system/tests/themes/test_legacy_theme/test_legacy_theme.theme b/core/modules/system/tests/themes/test_legacy_theme/test_legacy_theme.theme
new file mode 100644
index 0000000000000000000000000000000000000000..9802894955f4602f3f1268db87bb7037355ea554
--- /dev/null
+++ b/core/modules/system/tests/themes/test_legacy_theme/test_legacy_theme.theme
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Theme to test functionality of legacy theme functions.
+ *
+ * @todo Remove in https://www.drupal.org/project/drupal/issues/3097889
+ */
+
+/**
+ * Tests a theme overriding a suggestion of a base theme hook.
+ */
+function test_legacy_theme_theme_test_preprocess_suggestions__kitten__meerkat($variables) {
+  return 'Theme hook implementor=test_theme_theme_test__suggestion(). Foo=' . $variables['foo'];
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function test_legacy_theme_theme_suggestions_theme_test_function_suggestions_alter(array &$suggestions, array $variables) {
+  // Theme alter hooks run after module alter hooks, so add this theme
+  // suggestion to the beginning of the array so that the suggestion added by
+  // the theme_suggestions_test module can be picked up when that module is
+  // enabled.
+  array_unshift($suggestions, 'theme_test_function_suggestions__' . 'theme_override');
+}
+
+/**
+ * Returns HTML for a theme function suggestion test.
+ *
+ * Implements the theme_test_function_suggestions__theme_override suggestion.
+ */
+function test_legacy_theme_theme_test_function_suggestions__theme_override($variables) {
+  return 'Theme function overridden based on new theme suggestion provided by the test_legacy_theme theme.';
+}
+
+/**
+ * Returns HTML for a theme function suggestion test.
+ *
+ * Implements the theme_test_function_suggestions__module_override suggestion.
+ */
+function test_legacy_theme_theme_test_function_suggestions__module_override($variables) {
+  return 'Theme function overridden based on new theme suggestion provided by a module.';
+}
diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test--suggestion.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test--suggestion.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..a33e7c215384db79ea5cb19238b1f61e2b0c8f9e
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme/templates/theme-test--suggestion.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Theme hook implementor=theme-test--suggestion.html.twig. Foo={{ foo }}
diff --git a/core/modules/system/tests/themes/test_theme/test_theme.theme b/core/modules/system/tests/themes/test_theme/test_theme.theme
index a5af83bc8774be1baa95be0e37e943ab20bb2f72..d5bd12ed4e9de3b759cf5dca2a20b1691eadf2ef 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.theme
+++ b/core/modules/system/tests/themes/test_theme/test_theme.theme
@@ -12,13 +12,6 @@ function test_theme_preprocess_twig_theme_test_php_variables(&$variables) {
   $variables['php_values'] = _test_theme_twig_php_values();
 }
 
-/**
- * Tests a theme overriding a suggestion of a base theme hook.
- */
-function test_theme_theme_test__suggestion($variables) {
-  return 'Theme hook implementor=test_theme_theme_test__suggestion(). Foo=' . $variables['foo'];
-}
-
 /**
  * Implements hook_element_info_alter().
  */
@@ -75,35 +68,6 @@ function test_theme_theme_suggestions_theme_test_suggestions_alter(array &$sugge
   array_unshift($suggestions, 'theme_test_suggestions__' . 'theme_override');
 }
 
-/**
- * Implements hook_theme_suggestions_HOOK_alter().
- */
-function test_theme_theme_suggestions_theme_test_function_suggestions_alter(array &$suggestions, array $variables) {
-  // Theme alter hooks run after module alter hooks, so add this theme
-  // suggestion to the beginning of the array so that the suggestion added by
-  // the theme_suggestions_test module can be picked up when that module is
-  // enabled.
-  array_unshift($suggestions, 'theme_test_function_suggestions__' . 'theme_override');
-}
-
-/**
- * Returns HTML for a theme function suggestion test.
- *
- * Implements the theme_test_function_suggestions__theme_override suggestion.
- */
-function test_theme_theme_test_function_suggestions__theme_override($variables) {
-  return 'Theme function overridden based on new theme suggestion provided by the test_theme theme.';
-}
-
-/**
- * Returns HTML for a theme function suggestion test.
- *
- * Implements the theme_test_function_suggestions__module_override suggestion.
- */
-function test_theme_theme_test_function_suggestions__module_override($variables) {
-  return 'Theme function overridden based on new theme suggestion provided by a module.';
-}
-
 /**
  * Implements hook_theme_registry_alter().
  */
@@ -111,13 +75,6 @@ function test_theme_theme_registry_alter(&$registry) {
   $registry['theme_test_template_test']['variables']['additional'] = 'value';
 }
 
-/**
- * Tests a theme overriding a suggestion of a base theme hook.
- */
-function test_theme_theme_test_preprocess_suggestions__kitten__meerkat($variables) {
-  return 'Theme hook implementor=test_theme_theme_test__suggestion(). Foo=' . $variables['foo'];
-}
-
 /**
  * Tests a theme overriding a default hook with a suggestion.
  *
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/RegistryLegacyTest.php b/core/tests/Drupal/KernelTests/Core/Theme/RegistryLegacyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..98e22dce9a713c4a485bca164a229b8bfdd83a1a
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Theme/RegistryLegacyTest.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Theme;
+
+use Drupal\Core\Theme\Registry;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests legacy behavior of the ThemeRegistry class.
+ *
+ * @group Theme
+ * @group legacy
+ *
+ * @todo Remove in https://www.drupal.org/project/drupal/issues/3097889
+ */
+class RegistryLegacyTest extends KernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['theme_test', 'system'];
+
+  protected $profile = 'testing';
+
+  /**
+   * Tests the theme registry with theme functions and multiple subthemes.
+   *
+   * @expectedDeprecation Theme functions are deprecated in drupal:8.0.0 and are removed from drupal:10.0.0. Use Twig templates instead. See https://www.drupal.org/node/1831138
+   */
+  public function testMultipleSubThemes() {
+    $theme_handler = \Drupal::service('theme_handler');
+    \Drupal::service('module_installer')->install(['theme_legacy_test']);
+    \Drupal::service('theme_installer')->install(['test_basetheme']);
+
+    $registry_base_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_basetheme');
+    $registry_base_theme->setThemeManager(\Drupal::theme());
+
+    $preprocess_functions = $registry_base_theme->get()['theme_test_function_suggestions']['preprocess functions'];
+    $this->assertSame([
+      'template_preprocess_theme_test_function_suggestions',
+      'test_basetheme_preprocess_theme_test_function_suggestions',
+    ], $preprocess_functions, "Theme functions don't have template_preprocess but do have template_preprocess_HOOK");
+  }
+
+  /**
+   * Tests the theme registry with theme functions with suggestions.
+   *
+   * @expectedDeprecation Theme functions are deprecated in drupal:8.0.0 and are removed from drupal:10.0.0. Use Twig templates instead. See https://www.drupal.org/node/1831138
+   */
+  public function testSuggestionPreprocessFunctions() {
+    $theme_handler = \Drupal::service('theme_handler');
+    \Drupal::service('theme_installer')->install(['test_legacy_theme']);
+
+    $registry_deprecated_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_legacy_theme');
+    $registry_deprecated_theme->setThemeManager(\Drupal::theme());
+
+    $expected_preprocess_functions = [
+      'template_preprocess',
+      'theme_test_preprocess_theme_test_preprocess_suggestions',
+      'test_theme_preprocess_theme_test_preprocess_suggestions',
+      'test_theme_preprocess_theme_test_preprocess_suggestions__kitten',
+    ];
+
+    $preprocess_functions = $registry_deprecated_theme->get()['theme_test_preprocess_suggestions__kitten__meerkat']['preprocess functions'];
+    $this->assertSame($expected_preprocess_functions, $preprocess_functions, 'Suggestion implemented as a function correctly inherits preprocess functions.');
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php b/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php
index 41f028336bfb7ca92de4584a3463ee1eee567451..bfc693f32d689f1f21ebb012296fb8c794e422c2 100644
--- a/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Theme/RegistryTest.php
@@ -97,12 +97,6 @@ public function testMultipleSubThemes() {
       'template_preprocess',
       'test_basetheme_preprocess_theme_test_template_test',
     ], $preprocess_functions);
-
-    $preprocess_functions = $registry_base_theme->get()['theme_test_function_suggestions']['preprocess functions'];
-    $this->assertSame([
-       'template_preprocess_theme_test_function_suggestions',
-       'test_basetheme_preprocess_theme_test_function_suggestions',
-    ], $preprocess_functions, "Theme functions don't have template_preprocess but do have template_preprocess_HOOK");
   }
 
   /**
@@ -136,9 +130,6 @@ public function testSuggestionPreprocessFunctions() {
       'test_theme_preprocess_theme_test_preprocess_suggestions__kitten',
     ];
 
-    $preprocess_functions = $registry_theme->get()['theme_test_preprocess_suggestions__kitten__meerkat']['preprocess functions'];
-    $this->assertSame($expected_preprocess_functions, $preprocess_functions, 'Suggestion implemented as a function correctly inherits preprocess functions.');
-
     $preprocess_functions = $registry_theme->get()['theme_test_preprocess_suggestions__kitten__bearcat']['preprocess functions'];
     $this->assertSame($expected_preprocess_functions, $preprocess_functions, 'Suggestion implemented as a template correctly inherits preprocess functions.');
 
diff --git a/core/tests/Drupal/Tests/Core/Theme/RegistryLegacyTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryLegacyTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ee637fd39d1c2c8282debbe3026a6c538194e8b
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Theme/RegistryLegacyTest.php
@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Theme\RegistryLegacyTest.
+ */
+
+namespace Drupal\Tests\Core\Theme;
+
+use Drupal\Core\Theme\ActiveTheme;
+use Drupal\Core\Theme\Registry;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Theme\Registry
+ * @group Theme
+ * @group legacy
+ *
+ * @todo Remove in https://www.drupal.org/project/drupal/issues/3097889
+ */
+class RegistryLegacyTest extends UnitTestCase {
+
+  /**
+   * The tested theme registry.
+   *
+   * @var \Drupal\Tests\Core\Theme\TestRegistry
+   */
+  protected $registry;
+
+  /**
+   * The mocked cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit\Framework\MockObject\MockObject
+   */
+  protected $cache;
+
+  /**
+   * The mocked lock backend.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit\Framework\MockObject\MockObject
+   */
+  protected $lock;
+
+  /**
+   * The mocked module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit\Framework\MockObject\MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The mocked theme handler.
+   *
+   * @var \Drupal\Core\Extension\ThemeHandlerInterface|\PHPUnit\Framework\MockObject\MockObject
+   */
+  protected $themeHandler;
+
+  /**
+   * The mocked theme initialization.
+   *
+   * @var \Drupal\Core\Theme\ThemeInitializationInterface|\PHPUnit\Framework\MockObject\MockObject
+   */
+  protected $themeInitialization;
+
+  /**
+   * The theme manager.
+   *
+   * @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit\Framework\MockObject\MockObject
+   */
+  protected $themeManager;
+
+  /**
+   * The list of functions that get_defined_functions() should provide.
+   *
+   * @var array
+   */
+  public static $functions = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->cache = $this->createMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->lock = $this->createMock('Drupal\Core\Lock\LockBackendInterface');
+    $this->moduleHandler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->themeHandler = $this->createMock('Drupal\Core\Extension\ThemeHandlerInterface');
+    $this->themeInitialization = $this->createMock('Drupal\Core\Theme\ThemeInitializationInterface');
+    $this->themeManager = $this->createMock('Drupal\Core\Theme\ThemeManagerInterface');
+
+    $this->setupTheme();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function tearDown() {
+    parent::tearDown();
+    static::$functions = [];
+  }
+
+  /**
+   * Tests getting legacy theme function registry data defined by a module.
+   *
+   * @expectedDeprecation Theme functions are deprecated in drupal:8.0.0 and are removed from drupal:10.0.0. Use Twig templates instead. See https://www.drupal.org/node/1831138
+   */
+  public function testGetLegacyThemeFunctionRegistryForModule() {
+    $test_theme = new ActiveTheme([
+      'name' => 'test_legacy_theme',
+      'path' => 'core/modules/system/tests/themes/test_legacy_theme/test_legacy_theme.info.yml',
+      'engine' => 'twig',
+      'owner' => 'twig',
+      'stylesheets_remove' => [],
+      'libraries_override' => [],
+      'libraries_extend' => [],
+      'libraries' => [],
+      'extension' => '.twig',
+      'base_theme_extensions' => [],
+    ]);
+
+    $this->themeManager->expects($this->once())
+      ->method('getActiveTheme')
+      ->willReturn($test_theme);
+
+    // Include the module and theme files so that hook_theme can be called.
+    include_once $this->root . '/core/modules/system/tests/modules/theme_legacy_test/theme_legacy_test.module';
+    $this->moduleHandler->expects($this->once())
+      ->method('getImplementations')
+      ->with('theme')
+      ->will($this->returnValue(['theme_legacy_test']));
+    $this->moduleHandler->expects($this->atLeastOnce())
+      ->method('getModuleList')
+      ->willReturn([]);
+
+    $registry = $this->registry->get();
+
+    // Ensure that the registry entries from the module are found.
+    $this->assertArrayHasKey('theme_test', $registry);
+    $this->assertArrayHasKey('theme_test_function_suggestions', $registry);
+    $this->assertArrayHasKey('theme_test_foo', $registry);
+    $this->assertArrayHasKey('theme_test_render_element_children', $registry);
+    $this->assertArrayHasKey('theme_test_function_template_override', $registry);
+
+    $this->assertArrayNotHasKey('test_theme_not_existing_function', $registry);
+
+    $info = $registry['theme_test_function_suggestions'];
+    $this->assertEquals('module', $info['type']);
+    $this->assertEquals('core/modules/system/tests/modules/theme_legacy_test', $info['theme path']);
+    $this->assertEquals('theme_theme_test_function_suggestions', $info['function']);
+    $this->assertEquals([], $info['variables']);
+  }
+
+  protected function setupTheme() {
+    $this->registry = new TestRegistry($this->root, $this->cache, $this->lock, $this->moduleHandler, $this->themeHandler, $this->themeInitialization);
+    $this->registry->setThemeManager($this->themeManager);
+  }
+
+}
+
+class TestRegistry extends Registry {
+
+  protected function getPath($module) {
+    if ($module == 'theme_legacy_test') {
+      return 'core/modules/system/tests/modules/theme_legacy_test';
+    }
+  }
+
+}
+
+namespace Drupal\Core\Theme;
+
+use Drupal\Tests\Core\Theme\RegistryLegacyTest;
+
+/**
+ * Overrides get_defined_functions() with a configurable mock.
+ */
+function get_defined_functions() {
+  return RegistryLegacyTest::$functions ?: \get_defined_functions();
+}
diff --git a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
index f16b26f931217c1928e94ea93be86e52f4f0d2bd..c1f996293687604aa90474a1511ec5c0ad461023 100644
--- a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
+++ b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
@@ -151,21 +151,11 @@ public function testGetRegistryForModule() {
     $this->assertArrayHasKey('theme_test_suggestion_provided', $registry);
     $this->assertArrayHasKey('theme_test_specific_suggestions', $registry);
     $this->assertArrayHasKey('theme_test_suggestions', $registry);
-    $this->assertArrayHasKey('theme_test_function_suggestions', $registry);
     $this->assertArrayHasKey('theme_test_foo', $registry);
     $this->assertArrayHasKey('theme_test_render_element', $registry);
-    $this->assertArrayHasKey('theme_test_render_element_children', $registry);
-    $this->assertArrayHasKey('theme_test_function_template_override', $registry);
 
-    $this->assertArrayNotHasKey('test_theme_not_existing_function', $registry);
     $this->assertFalse(in_array('test_stable_preprocess_theme_test_render_element', $registry['theme_test_render_element']['preprocess functions']));
 
-    $info = $registry['theme_test_function_suggestions'];
-    $this->assertEquals('module', $info['type']);
-    $this->assertEquals('core/modules/system/tests/modules/theme_test', $info['theme path']);
-    $this->assertEquals('theme_theme_test_function_suggestions', $info['function']);
-    $this->assertEquals([], $info['variables']);
-
     // The second call will initialize with the second theme. Ensure that this
     // returns a different object and the discovery for the second theme's
     // preprocess function worked.