diff --git a/src/Commands/MigrateToolsCommands.php b/src/Commands/MigrateToolsCommands.php
index d1c5f0f..2931cbb 100644
--- a/src/Commands/MigrateToolsCommands.php
+++ b/src/Commands/MigrateToolsCommands.php
@@ -21,6 +21,8 @@ use Drush\Commands\DrushCommands;
  */
 class MigrateToolsCommands extends DrushCommands {
 
+  public const ID_LIST_DELIMITER = ':';
+
   /**
    * Migration plugin manager service.
    *
@@ -121,13 +123,12 @@ class MigrateToolsCommands extends DrushCommands {
    * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
    *   Migrations status formatted as table.
    */
-  public function status($migration_names = '', array $options = []) {
-    $options += [
-      'group' => NULL,
-      'tag' => NULL,
-      'names-only' => NULL,
-    ];
-    $names_only = $options['names-only'];
+  public function status($migration_names = '', array $options = [
+    'group' => self::REQ,
+    'tag' => self::REQ,
+    'names-only' => FALSE,
+  ]) {
+    $names_only = $options['names-only'] ?? FALSE;
 
     $migrations = $this->migrationsList($migration_names, $options);
 
@@ -278,22 +279,21 @@ class MigrateToolsCommands extends DrushCommands {
    * @throws \Exception
    *   If there are not enough parameters to the command.
    */
-  public function import($migration_names = '', array $options = []) {
-    $options += [
-      'all' => NULL,
-      'group' => NULL,
-      'tag' => NULL,
-      'limit' => NULL,
-      'feedback' => NULL,
-      'idlist' => NULL,
-      'idlist-delimiter' => ':',
-      'update' => NULL,
-      'force' => NULL,
-      'execute-dependencies' => NULL,
-    ];
-    $group_names = $options['group'];
-    $tag_names = $options['tag'];
-    $all = $options['all'];
+  public function import($migration_names = '', array $options = [
+    'all' => FALSE,
+    'group' => self::REQ,
+    'tag' => self::REQ,
+    'limit' => self::REQ,
+    'feedback' => self::REQ,
+    'idlist' => self::REQ,
+    'idlist-delimiter' => self::ID_LIST_DELIMITER,
+    'update' => FALSE,
+    'force' => FALSE,
+    'execute-dependencies' => FALSE,
+  ]) {
+    $group_names = $options['group'] ?? NULL;
+    $tag_names = $options['tag'] ?? NULL;
+    $all = isset($options['all']);
     $additional_options = [];
     if (!$all && !$group_names && !$migration_names && !$tag_names) {
       throw new \Exception(dt('You must specify --all, --group, --tag or one or more migration names separated by commas'));
@@ -309,7 +309,7 @@ class MigrateToolsCommands extends DrushCommands {
       'execute-dependencies',
     ];
     foreach ($possible_options as $option) {
-      if ($options[$option]) {
+      if (isset($options[$option])) {
         $additional_options[$option] = $options[$option];
       }
     }
@@ -343,7 +343,6 @@ class MigrateToolsCommands extends DrushCommands {
    * @option group A comma-separated list of migration groups to rollback
    * @option tag ID of the migration tag to rollback
    * @option feedback Frequency of progress messages, in items processed
-   * @option feedback Frequency of progress messages, in items processed
    * @option idlist Comma-separated list of IDs to rollback
    * @option idlist-delimiter The delimiter for records, defaults to ':'
    *
@@ -368,25 +367,24 @@ class MigrateToolsCommands extends DrushCommands {
    * @throws \Exception
    *   If there are not enough parameters to the command.
    */
-  public function rollback($migration_names = '', array $options = []) {
-    $options += [
-      'all' => NULL,
-      'group' => NULL,
-      'tag' => NULL,
-      'feedback' => NULL,
-      'idlist' => NULL,
-      'idlist-delimiter' => ':',
-    ];
-    $group_names = $options['group'];
-    $tag_names = $options['tag'];
-    $all = $options['all'];
+  public function rollback($migration_names = '', array $options = [
+    'all' => FALSE,
+    'group' => self::REQ,
+    'tag' => self::REQ,
+    'feedback' => self::REQ,
+    'idlist' => self::REQ,
+    'idlist-delimiter' => self::ID_LIST_DELIMITER,
+  ]) {
+    $group_names = $options['group'] ?? [];
+    $tag_names = $options['tag'] ?? [];
+    $all = isset($options['all']);
     $additional_options = [];
     if (!$all && !$group_names && !$migration_names && !$tag_names) {
       throw new \Exception(dt('You must specify --all, --group, --tag, or one or more migration names separated by commas'));
     }
 
     foreach (['feedback', 'idlist', 'idlist-delimiter'] as $option) {
-      if ($options[$option]) {
+      if (isset($options[$option])) {
         $additional_options[$option] = $options[$option];
       }
     }
@@ -544,12 +542,11 @@ class MigrateToolsCommands extends DrushCommands {
    * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
    *   Source fields of the given migration formatted as a table.
    */
-  public function messages($migration_id, array $options = []) {
-    $options += [
-      'csv' => NULL,
-      'idlist' => NULL,
-      'idlist-delimiter' => ':',
-    ];
+  public function messages($migration_id, array $options = [
+    'csv' => FALSE,
+    'idlist' => self::REQ,
+    'idlist-delimiter' => self::ID_LIST_DELIMITER,
+  ]) {
     /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
     $migration = $this->migrationPluginManager->createInstance(
       $migration_id
@@ -562,7 +559,9 @@ class MigrateToolsCommands extends DrushCommands {
     $id_list = MigrateTools::buildIdList($options);
     $map = new IdMapFilter($migration->getIdMap(), $id_list);
     $table = [];
-    foreach ($map->getMessageIterator() as $row) {
+    // TODO: Remove after 8.7 support goes away.
+    $iterator_method = method_exists($map, 'getMessages') ? 'getMessages' : 'getMessageIterator';
+    foreach ($map->{$iterator_method}() as $row) {
       unset($row->msgid);
       $table[] = (array) $row;
     }
@@ -571,7 +570,7 @@ class MigrateToolsCommands extends DrushCommands {
       return NULL;
     }
 
-    if ($options['csv']) {
+    if (isset($options['csv'])) {
       fputcsv(STDOUT, array_keys($table[0]));
       foreach ($table as $row) {
         fputcsv(STDOUT, $row);
@@ -644,11 +643,11 @@ class MigrateToolsCommands extends DrushCommands {
    */
   protected function migrationsList($migration_ids = '', array $options = []) {
     // Filter keys must match the migration configuration property name.
-    $filter['migration_group'] = $options['group'] ? explode(
+    $filter['migration_group'] = isset($options['group']) ? explode(
       ',',
       $options['group']
     ) : [];
-    $filter['migration_tags'] = $options['tag'] ? explode(
+    $filter['migration_tags'] = isset($options['tag']) ? explode(
       ',',
       $options['tag']
     ) : [];
@@ -694,15 +693,14 @@ class MigrateToolsCommands extends DrushCommands {
           foreach ($values as $search_value) {
             foreach ($matched_migrations as $id => $migration) {
               // Cast to array because migration_tags can be an array.
-              $configured_values = (array) $migration->get($property);
-              $configured_id = (in_array(
-                $search_value,
-                $configured_values
-              )) ? $search_value : 'default';
-              if (empty($search_value) || $search_value == $configured_id) {
+              $definition = $migration->getPluginDefinition();
+              $configured_values = (array) ($definition[$property] ?? NULL);
+              $configured_id = in_array($search_value, $configured_values, TRUE) ? $search_value : 'default';
+              if (empty($search_value) || $search_value === $configured_id) {
                 if (empty($migration_ids) || in_array(
                     mb_strtolower($id),
-                    $migration_ids
+                    $migration_ids,
+                    TRUE
                   )) {
                   $filtered_migrations[$id] = $migration;
                 }
@@ -748,7 +746,8 @@ class MigrateToolsCommands extends DrushCommands {
     static $executed_migrations = [];
 
     if (isset($options['execute-dependencies'])) {
-      $required_migrations = $migration->get('requirements');
+      $definition = $migration->getPluginDefinition();
+      $required_migrations = $definition['requirements'] ?? [];
       $required_migrations = array_filter($required_migrations, function ($value) use ($executed_migrations) {
         return !isset($executed_migrations[$value]);
       });
diff --git a/src/MigrateExecutable.php b/src/MigrateExecutable.php
index a05b3fa..77c8a1e 100644
--- a/src/MigrateExecutable.php
+++ b/src/MigrateExecutable.php
@@ -375,7 +375,7 @@ class MigrateExecutable extends MigrateExecutableBase {
         throw new MigrateSkipRowException('Skipped due to idlist.', FALSE);
       }
     }
-    if ($this->feedback && ($this->counter) && $this->counter % $this->feedback == 0) {
+    if ($this->feedback && $this->counter && $this->counter % $this->feedback == 0) {
       $this->progressMessage(FALSE);
       $this->resetCounters();
     }
diff --git a/tests/src/Functional/DrushCommandsTest.php b/tests/src/Functional/DrushCommandsTest.php
new file mode 100644
index 0000000..258b619
--- /dev/null
+++ b/tests/src/Functional/DrushCommandsTest.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\Tests\migrate_tools\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+use Drush\TestTraits\DrushTestTrait;
+
+/**
+ * Execute drush on fully functional website.
+ *
+ * @group migrate_tools
+ */
+class DrushCommandsTest extends BrowserTestBase {
+  use DrushTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'migrate_tools_test',
+    'migrate_tools',
+    'migrate_plus',
+    'taxonomy',
+    'text',
+    'system',
+    'user',
+  ];
+
+  /**
+   * Tests migrate:import with feedback.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public function testFeedback() {
+    $this->drush('mim', ['fruit_terms'], ['feedback' => 2]);
+    $output = $this->getErrorOutputAsList();
+    $expected = [
+      '[notice] Processed 2 items (2 created, 0 updated, 0 failed, 0 ignored) - continuing with \'fruit_terms\'',
+      ' [notice] Processed 1 item (1 created, 0 updated, 0 failed, 0 ignored) - done with \'fruit_terms\'',
+    ];
+    $this->assertEquals($expected, $output);
+  }
+
+  /**
+   * Tests migrate:import with limit.
+   */
+  public function testLimit() {
+    $this->drush('mim', ['fruit_terms'], ['limit' => 2]);
+    $output = $this->getErrorOutputAsList();
+    $expected = [
+      '[notice] Processed 2 items (2 created, 0 updated, 0 failed, 0 ignored) - done with \'fruit_terms\'',
+    ];
+    $this->assertEquals($expected, $output);
+  }
+
+  /**
+   * Tests many of the migrate drush commands.
+   */
+  public function testDrush() {
+    $this->drush('ms', [], [], NULL, NULL, 1);
+    $this->assertContains('The "does_not_exist" plugin does not exist.', $this->getErrorOutput());
+    $this->drush('cdel', ['migrate_plus.migration.invalid_plugin']);
+    // Flush cache so the recently removed invalid migration is cleared.
+    $this->drush('cr');
+    $this->drush('ms');
+    $this->assertContains('Default (default)   fruit_terms        Idle     3       0          3', $this->getOutput());
+    $this->drush('mim', ['fruit_terms']);
+    $this->assertErrorOutputEquals('[notice] Processed 3 items (3 created, 0 updated, 0 failed, 0 ignored) - done with \'fruit_terms\'');
+    $this->drush('mim', ['fruit_terms'], [
+      'update' => NULL,
+      'force' => NULL,
+      'execute-dependencies' => NULL,
+    ]);
+    $this->assertErrorOutputEquals('[notice] Processed 3 items (0 created, 3 updated, 0 failed, 0 ignored) - done with \'fruit_terms\'');
+    $this->drush('mrs', ['fruit_terms']);
+    $this->assertErrorOutputEquals('[warning] Migration fruit_terms is already Idle');
+    $this->drush('mfs', ['fruit_terms']);
+    $this->assertContains('name           name', $this->getOutput());
+    $this->drush('mmsg', ['fruit_terms']);
+    $this->assertErrorOutputEquals('[notice] No messages for this migration');
+    $this->drush('mr', ['fruit_terms']);
+    $this->assertErrorOutputEquals('[notice] Rolled back 3 items - done with \'fruit_terms\'');
+    $this->drush('migrate:stop', ['fruit_terms']);
+    $this->assertErrorOutputEquals('[warning] Migration fruit_terms is idle');
+  }
+
+}
diff --git a/tests/src/Kernel/DrushTest.php b/tests/src/Kernel/DrushTest.php
index 7ecaa90..b9b065d 100644
--- a/tests/src/Kernel/DrushTest.php
+++ b/tests/src/Kernel/DrushTest.php
@@ -73,7 +73,9 @@ class DrushTest extends MigrateTestBase {
     $this->installConfig('migrate_plus');
     $this->installConfig('migrate_tools_test');
     $this->installEntitySchema('taxonomy_term');
+    $this->installEntitySchema('user');
     $this->installSchema('system', ['key_value', 'key_value_expire']);
+    $this->installSchema('user', ['users_data']);
     $this->migrationPluginManager = $this->container->get('plugin.manager.migration');
     $this->logger = $this->container->get('logger.channel.migrate_tools');
     $this->commands = new MigrateToolsCommands(
@@ -90,7 +92,7 @@ class DrushTest extends MigrateTestBase {
   public function testStatus() {
     $this->executeMigration('fruit_terms');
     /** @var \Consolidation\OutputFormatters\StructuredData\RowsOfFields $result */
-    $result = $this->commands->status('fruit_terms');
+    $result = $this->commands->status('fruit_terms', []);
     $rows = $result->getArrayCopy();
     $this->assertSame(1, count($rows));
     $row = reset($rows);
@@ -98,6 +100,14 @@ class DrushTest extends MigrateTestBase {
     $this->assertSame(3, $row['total']);
     $this->assertSame(3, $row['imported']);
     $this->assertSame('Idle', $row['status']);
+
+    // Migrate status should not display migrate_drupal migrations if no source
+    // database is defined.
+    \Drupal::service('module_installer')->uninstall(['migrate_tools_test']);
+    $this->enableModules(['migrate_drupal']);
+    \Drupal::configFactory()->getEditable('migrate_plus.migration.fruit_terms')->delete();
+    $rows = $this->commands->status();
+    $this->assertEmpty($rows);
   }
 
   /**
@@ -120,7 +130,7 @@ class DrushTest extends MigrateTestBase {
     $id_map = $migration->getIdMap();
     $this->commands->import('fruit_terms', ['idlist' => 'Apple'] + $this->importBaseOptions);
     $this->assertSame(1, $id_map->importedCount());
-    $this->commands->import('fruit_terms');
+    $this->commands->import('fruit_terms', []);
     $this->assertSame(3, $id_map->importedCount());
     $this->commands->import('fruit_terms', ['idlist' => 'Apple', 'update' => TRUE] + $this->importBaseOptions);
     $this->assertSame(0, count($id_map->getRowsNeedingUpdate(100)));
@@ -132,7 +142,7 @@ class DrushTest extends MigrateTestBase {
   public function testFailingImportThrowsException() {
     $this->expectException(\Exception::class);
     $this->expectExceptionMessage('source_exception migration failed.');
-    $this->commands->import('source_exception');
+    $this->commands->import('source_exception', []);
   }
 
   /**
@@ -148,7 +158,7 @@ class DrushTest extends MigrateTestBase {
     $id_map = $migration->getIdMap();
     $id_map->saveMessage(['name' => 'Apple'], $message);
     /** @var \Consolidation\OutputFormatters\StructuredData\RowsOfFields $result */
-    $result = $this->commands->messages('fruit_terms');
+    $result = $this->commands->messages('fruit_terms', []);
     $rows = $result->getArrayCopy();
     $this->assertSame($message, $rows[0]['message']);
   }
@@ -171,7 +181,7 @@ class DrushTest extends MigrateTestBase {
     $migration = $this->migrationPluginManager->createInstance('fruit_terms');
     $id_map = $migration->getIdMap();
     $this->assertSame(3, $id_map->importedCount());
-    $this->commands->rollback('fruit_terms');
+    $this->commands->rollback('fruit_terms', []);
     $this->assertSame(0, $id_map->importedCount());
   }
 
@@ -184,7 +194,7 @@ class DrushTest extends MigrateTestBase {
     /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
     $migration = $this->migrationPluginManager->createInstance('source_exception');
     $migration->setStatus(MigrationInterface::STATUS_IMPORTING);
-    $this->commands->rollback('source_exception');
+    $this->commands->rollback('source_exception', []);
   }
 
   /**
@@ -196,7 +206,7 @@ class DrushTest extends MigrateTestBase {
     /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
     $migration = $this->migrationPluginManager->createInstance('fruit_terms');
     $migration->setStatus(MigrationInterface::STATUS_IMPORTING);
-    $this->assertSame('Importing', $this->commands->status('fruit_terms')->getArrayCopy()[0]['status']);
+    $this->assertSame('Importing', $this->commands->status('fruit_terms', [])->getArrayCopy()[0]['status']);
     $this->commands->resetStatus('fruit_terms');
     $this->assertSame(MigrationInterface::STATUS_IDLE, $migration->getStatus());
 
