diff --git a/core/modules/migrate/src/Plugin/MigrateSourcePluginManager.php b/core/modules/migrate/src/Plugin/MigrateSourcePluginManager.php
index 965da5dd76..c83db14331 100644
--- a/core/modules/migrate/src/Plugin/MigrateSourcePluginManager.php
+++ b/core/modules/migrate/src/Plugin/MigrateSourcePluginManager.php
@@ -7,6 +7,7 @@
 use Drupal\migrate\Plugin\Discovery\AnnotatedClassDiscoveryAutomatedProviders;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\migrate\Plugin\Discovery\ProviderFilterDecorator;
+use Drupal\migrate\Plugin\Exception\BadPluginDefinitionException;
 
 /**
  * Plugin manager for migrate source plugins.
@@ -70,4 +71,15 @@ protected function findDefinitions() {
     });
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function processDefinition(&$definition, $plugin_id) {
+    parent::processDefinition($definition, $plugin_id);
+
+    if (empty($definition['source_module'])) {
+      throw new BadPluginDefinitionException($plugin_id, 'source_module');
+    }
+  }
+
 }
diff --git a/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php b/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php
index 8cee6ddbe4..2be0b5ee53 100644
--- a/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php
+++ b/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php
@@ -38,7 +38,8 @@
  * @see \Drupal\migrate\Plugin\MigrateSourceInterface
  *
  * @MigrateSource(
- *   id = "embedded_data"
+ *   id = "embedded_data",
+ *   source_module = "migrate"
  * )
  */
 class EmbeddedDataSource extends SourcePluginBase {
diff --git a/core/modules/migrate/src/Plugin/migrate/source/EmptySource.php b/core/modules/migrate/src/Plugin/migrate/source/EmptySource.php
index 5f44035e25..15c603e36d 100644
--- a/core/modules/migrate/src/Plugin/migrate/source/EmptySource.php
+++ b/core/modules/migrate/src/Plugin/migrate/source/EmptySource.php
@@ -19,7 +19,8 @@
  * elements, with values of 'user' and 'image', respectively.
  *
  * @MigrateSource(
- *   id = "empty"
+ *   id = "empty",
+ *   source_module = "migrate",
  * )
  */
 class EmptySource extends SourcePluginBase {
diff --git a/core/modules/migrate/tests/modules/migrate_high_water_test/src/Plugin/migrate/source/HighWaterTest.php b/core/modules/migrate/tests/modules/migrate_high_water_test/src/Plugin/migrate/source/HighWaterTest.php
index 601bbd942b..50b70cdfe5 100644
--- a/core/modules/migrate/tests/modules/migrate_high_water_test/src/Plugin/migrate/source/HighWaterTest.php
+++ b/core/modules/migrate/tests/modules/migrate_high_water_test/src/Plugin/migrate/source/HighWaterTest.php
@@ -8,7 +8,8 @@
  * Source plugin for migration high water tests.
  *
  * @MigrateSource(
- *   id = "high_water_test"
+ *   id = "high_water_test",
+ *   source_module = "migrate_high_water_test",
  * )
  */
 class HighWaterTest extends SqlBase {
diff --git a/core/modules/migrate/tests/modules/migrate_query_batch_test/src/Plugin/migrate/source/QueryBatchTest.php b/core/modules/migrate/tests/modules/migrate_query_batch_test/src/Plugin/migrate/source/QueryBatchTest.php
index cc00c0dfa3..c1dd22d252 100644
--- a/core/modules/migrate/tests/modules/migrate_query_batch_test/src/Plugin/migrate/source/QueryBatchTest.php
+++ b/core/modules/migrate/tests/modules/migrate_query_batch_test/src/Plugin/migrate/source/QueryBatchTest.php
@@ -8,7 +8,8 @@
  * Source plugin for migration high water tests.
  *
  * @MigrateSource(
- *   id = "query_batch_test"
+ *   id = "query_batch_test",
+ *   source_module = "migrate_query_batch_test",
  * )
  */
 class QueryBatchTest extends SqlBase {
diff --git a/core/modules/migrate/tests/src/Kernel/Plugin/MigrationProvidersExistTest.php b/core/modules/migrate/tests/src/Kernel/Plugin/MigrationProvidersExistTest.php
index 19a8b24777..5e4751c7d9 100644
--- a/core/modules/migrate/tests/src/Kernel/Plugin/MigrationProvidersExistTest.php
+++ b/core/modules/migrate/tests/src/Kernel/Plugin/MigrationProvidersExistTest.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Tests\migrate\Kernel\Plugin;
 
-use Drupal\Component\Render\FormattableMarkup;
 use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait;
 use Drupal\migrate\Plugin\Exception\BadPluginDefinitionException;
 use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManager;
@@ -18,50 +17,43 @@ class MigrationProvidersExistTest extends MigrateDrupalTestBase {
   use FileSystemModuleDiscoveryDataProviderTrait;
 
   /**
-   * {@inheritdoc}
+   * Tests that a missing source_module property raises an exception.
    */
-  public static $modules = ['migration_provider_test'];
+  public function testSourceProvider() {
+    $this->enableModules(['migration_provider_test']);
+    $this->setExpectedException(BadPluginDefinitionException::class, 'The no_source_module plugin should define the source_module property.');
+    $this->container->get('plugin.manager.migrate.source')->getDefinitions();
+  }
 
   /**
-   * Tests that modules exist for all source and destination plugins.
+   * Tests that modules exist for all source plugins.
    */
   public function testProvidersExist() {
+    $this->enableAllModules();
+
+    /** @var \Drupal\migrate\Plugin\MigrationPluginManager $plugin_manager */
+    $plugin_manager = $this->container->get('plugin.manager.migration');
+
+    // Instantiate all migrations.
+    $migrations = array_keys($plugin_manager->getDefinitions());
+    $migrations = $plugin_manager->createInstances($migrations);
+
+    /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
+    foreach ($migrations as $migration) {
+      $this->assertInternalType('string', $migration->getSourcePlugin()->getSourceModule());
+    }
+  }
+
+  /**
+   * Enable all available modules.
+   */
+  protected function enableAllModules() {
     // Install all available modules.
     $module_handler = $this->container->get('module_handler');
     $modules = $this->coreModuleListDataProvider();
     $modules_enabled = $module_handler->getModuleList();
     $modules_to_enable = array_keys(array_diff_key($modules, $modules_enabled));
     $this->enableModules($modules_to_enable);
-
-    /** @var \Drupal\migrate\Plugin\MigrationPluginManager $plugin_manager */
-    $plugin_manager = $this->container->get('plugin.manager.migration');
-    // Get all the migrations.
-    $migrations = $plugin_manager->createInstances(array_keys($plugin_manager->getDefinitions()));
-    // Ensure the test module was enabled.
-    $this->assertTrue(array_key_exists('migration_provider_test', $migrations));
-    $this->assertTrue(array_key_exists('migration_provider_no_annotation', $migrations));
-    /** @var \Drupal\migrate\Plugin\Migration $migration */
-    foreach ($migrations as $migration) {
-      $source_module = $migration->getSourcePlugin()->getSourceModule();
-      $destination_module = $migration->getDestinationPlugin()->getDestinationModule();
-      $migration_id = $migration->getPluginId();
-      if ($migration_id == 'migration_provider_test') {
-        $this->assertFalse($source_module, new FormattableMarkup('Source module not found for @migration_id.', ['@migration_id' => $migration_id]));
-        $this->assertFalse($destination_module, new FormattableMarkup('Destination module not found for @migration_id.', ['@migration_id' => $migration_id]));
-      }
-      elseif ($migration_id == 'migration_provider_no_annotation') {
-        $this->assertFalse($source_module, new FormattableMarkup('Source module not found for @migration_id.', ['@migration_id' => $migration_id]));
-        $this->assertTrue($destination_module, new FormattableMarkup('Destination module found for @migration_id.', ['@migration_id' => $migration_id]));
-      }
-      else {
-        $this->assertTrue($source_module, new FormattableMarkup('Source module found for @migration_id.', ['@migration_id' => $migration_id]));
-        $this->assertTrue($destination_module, new FormattableMarkup('Destination module found for @migration_id.', ['@migration_id' => $migration_id]));
-      }
-      // Destination module can't be migrate or migrate_drupal or
-      // migrate_drupal_ui.
-      $invalid_destinations = ['migrate', 'migrate_drupal', 'migrate_drupal_ui'];
-      $this->assertNotContains($destination_module, $invalid_destinations, new FormattableMarkup('Invalid destination for @migration_id.', ['@migration_id' => $migration_id]));
-    }
   }
 
   /**
@@ -150,16 +142,9 @@ public function testFieldProvidersExist() {
         'destination_module' => 'core',
       ],
     ];
-    // Install all available modules.
-    $module_handler = $this->container->get('module_handler');
-    $modules = $this->coreModuleListDataProvider();
-    $modules_enabled = $module_handler->getModuleList();
-    $modules_to_enable = array_keys(array_diff_key($modules, $modules_enabled));
-    $this->enableModules($modules_to_enable);
+    $this->enableAllModules();
 
-    /** @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManager $plugin_manager */
-    $plugin_manager = $this->container->get('plugin.manager.migrate.field');
-    $definitions = $plugin_manager->getDefinitions();
+    $definitions = $this->container->get('plugin.manager.migrate.field')->getDefinitions();
     foreach ($definitions as $key => $definition) {
       $this->assertArrayHasKey($key, $expected_mappings);
       $this->assertEquals($expected_mappings[$key]['source_module'], $definition['source_module']);
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/EmptySource.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/EmptySource.php
index e0acc4b52e..0c49d048cd 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/EmptySource.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/EmptySource.php
@@ -14,7 +14,8 @@
  * Source returning an empty row with Drupal specific config dependencies.
  *
  * @MigrateSource(
- *   id = "md_empty"
+ *   id = "md_empty",
+ *   source_module = "system",
  * )
  */
 class EmptySource extends BaseEmptySource implements ContainerFactoryPluginInterface, DependentPluginInterface {
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php
index e127dfbdc8..a585510b0f 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/Variable.php
@@ -13,7 +13,8 @@
  * example for any normal source class returning multiple rows.
  *
  * @MigrateSource(
- *   id = "variable"
+ *   id = "variable",
+ *   source_module = "system",
  * )
  */
 class Variable extends DrupalSqlBase {
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php
index 61da5eb30a..47d575c533 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/VariableMultiRow.php
@@ -11,7 +11,8 @@
  * variable.
  *
  * @MigrateSource(
- *   id = "variable_multirow"
+ *   id = "variable_multirow",
+ *   source_module = "system",
  * )
  */
 class VariableMultiRow extends DrupalSqlBase {
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php
index f2ca8b73aa..ce57716a03 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/VariableTranslation.php
@@ -11,7 +11,8 @@
  * Drupal i18n_variable source from database.
  *
  * @MigrateSource(
- *   id = "variable_translation"
+ *   id = "variable_translation",
+ *   source_module = "system",
  * )
  */
 class VariableTranslation extends DrupalSqlBase {
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php
index dd8d0f0741..f1c338a327 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php
@@ -8,7 +8,8 @@
  * Drupal i18n_variable source from database.
  *
  * @MigrateSource(
- *   id = "i18n_variable"
+ *   id = "i18n_variable",
+ *   source_module = "system",
  * )
  *
  * @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php
index 8d8338776d..c396b7677d 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d8/Config.php
@@ -9,7 +9,8 @@
  * Drupal config source from database.
  *
  * @MigrateSource(
- *   id = "d8_config"
+ *   id = "d8_config",
+ *   source_module = "system",
  * )
  */
 class Config extends DrupalSqlBase {
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php
index 09dc9691e3..6f83631127 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php
@@ -114,11 +114,12 @@ protected function tearDown() {
   public function testMigrateUpgrade() {
     $connection_options = $this->sourceDatabase->getConnectionOptions();
     $this->drupalGet('/upgrade');
-    $this->assertSession()->responseContains('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.');
+    $web_assert = $this->assertSession();
+    $web_assert->responseContains('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.');
 
     $this->drupalPostForm(NULL, [], t('Continue'));
-    $this->assertText('Provide credentials for the database of the Drupal site you want to upgrade.');
-    $this->assertFieldByName('mysql[host]');
+    $web_assert->pageTextContains('Provide credentials for the database of the Drupal site you want to upgrade.');
+    $web_assert->fieldExists('mysql[host]');
 
     $driver = $connection_options['driver'];
     $connection_options['prefix'] = $connection_options['prefix']['default'];
@@ -148,36 +149,34 @@ public function testMigrateUpgrade() {
     // Ensure submitting the form with invalid database credentials gives us a
     // nice warning.
     $this->drupalPostForm(NULL, [$driver . '[database]' => 'wrong'] + $edits, t('Review upgrade'));
-    $this->assertText('Resolve the issue below to continue the upgrade.');
+    $web_assert->pageTextContains('Resolve the issue below to continue the upgrade.');
 
     $this->drupalPostForm(NULL, $edits, t('Review upgrade'));
-    $this->assertResponse(200);
-    $this->assertText('Upgrade analysis report');
+    $web_assert->statusCodeEquals(200);
+
     // Ensure we get errors about missing modules.
-    $this->assertSession()->pageTextContains(t('Source module not found for migration_provider_no_annotation.'));
-    $this->assertSession()->pageTextContains(t('Source module not found for migration_provider_test.'));
-    $this->assertSession()->pageTextContains(t('Destination module not found for migration_provider_test'));
+    $web_assert->pageTextContains(t('Resolve the issue below to continue the upgrade'));
+    $web_assert->pageTextContains(t('The no_source_module plugin should define the source_module property.'));
 
     // Uninstall the module causing the missing module error messages.
     $this->container->get('module_installer')->uninstall(['migration_provider_test'], TRUE);
 
     // Restart the upgrade process.
     $this->drupalGet('/upgrade');
-    $this->assertSession()->responseContains('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.');
+    $web_assert->responseContains('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.');
 
     $this->drupalPostForm(NULL, [], t('Continue'));
-    $this->assertSession()->pageTextContains('Provide credentials for the database of the Drupal site you want to upgrade.');
-    $this->assertSession()->fieldExists('mysql[host]');
+    $web_assert->pageTextContains('Provide credentials for the database of the Drupal site you want to upgrade.');
+    $web_assert->fieldExists('mysql[host]');
 
     $this->drupalPostForm(NULL, $edits, t('Review upgrade'));
-    $this->assertSession()->statusCodeEquals(200);
-    $this->assertSession()->pageTextContains('Upgrade analysis report');
+    $web_assert->statusCodeEquals(200);
+    $web_assert->pageTextContains('Upgrade analysis report');
     // Ensure there are no errors about the missing modules from the test module.
-    $this->assertSession()->pageTextNotContains(t('Source module not found for migration_provider_no_annotation.'));
-    $this->assertSession()->pageTextNotContains(t('Source module not found for migration_provider_test.'));
-    $this->assertSession()->pageTextNotContains(t('Destination module not found for migration_provider_test'));
+    $web_assert->pageTextNotContains(t('Source module not found for migration_provider_no_annotation.'));
+    $web_assert->pageTextNotContains(t('Source module not found for migration_provider_test.'));
     // Ensure there are no errors about any other missing migration providers.
-    $this->assertSession()->pageTextNotContains(t('module not found'));
+    $web_assert->pageTextNotContains(t('module not found'));
 
     // Test the available migration paths.
     $all_available = $this->getAvailablePaths();
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php
index 3cfe4bc6d7..e4235964fe 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php
@@ -97,7 +97,6 @@ protected function getAvailablePaths() {
       'email',
       'entityreference',
       'field',
-      'field_sql_storage',
       'file',
       'filefield',
       'filter',
