Writing Tests for Drush Commands
There are plenty of examples of these in the wild, but I figured I’d show a stripped down version of an automated Kernel test that successfully tests a drush command. The trick here is making sure you set up a logger and that you stub a few methods (if you happen to use $this->logger() and dt() in your Drush commands). Also featured in this example is the use of Faker to generate realistic test data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
<?php declare(strict_types = 1); namespace Drupal\mymodule\Tests\Kernel { use Drupal\KernelTests\KernelTestBase; use Drupal\mymodule\Drush\Commands\QueueCommands; use Drush\Log\DrushLoggerManager; use Faker\Factory; use Faker\Generator; use Psr\Log\LoggerInterface; /** * Tests the QueueCommands Drush command file. */ final class QueueTest extends KernelTestBase { /** * {@inheritdoc} */ protected static $modules = [ // Enable all the modules that this test relies on. 'auto_entitylabel', 'block', 'datetime', 'eck', 'field', 'file', 'image', 'link', 'mymodule', 'mysql', 'options', 'path', 'path_alias', 'system', 'text', 'user', 'views', 'views_bulk_operations', ]; /** * The Faker generator. * * @var \Faker\Generator */ protected Generator $faker; /** * The logger. * * @var \Drush\Log\DrushLoggerManager|\Psr\Log\LoggerInterface */ protected DrushLoggerManager|LoggerInterface $logger; /** * The Drush queue commands. * * @var \Drupal\mymodule\Drush\Commands\QueueCommands */ protected QueueCommands $drushQueueCommands; /** * Set up the test environment. */ protected function setUp(): void { parent::setUp(); // Install required config. $this->installConfig(['mymodule']); $this->faker = Factory::create(); $logger_class = class_exists(DrushLoggerManager::class) ? DrushLoggerManager::class : LoggerInterface::class; $this->logger = $this->prophesize($logger_class)->reveal(); $drushQueueCommands = new QueueCommands(); $drushQueueCommands->setLogger($this->logger); } /** * Tests the mymodule:close-all-registration-entities drush command. */ public function testCloseAllRegistrationEntities() { // Create a single registration entity. // Just demoing simple Faker usage for blog post. $registration = \Drupal::entityTypeManager() ->getStorage('registration') ->create([ 'type' => 'appt', 'field_echeckin_link' => $this->faker->url(), 'field_epic_id' => $this->faker->randomNumber(9), 'field_first_name' => $this->faker->firstName(), 'field_has_echeckin_link' => $this->faker->boolean(), 'field_outcome' => NULL, 'field_phone_number' => $this->faker->phoneNumber(), 'field_sequence_is_open' => 1, ]); $registration->save(); // This method exists in my module's Drush command file. // It sets field_sequence_is_open field to 0 for all open reg entities. // drush mymodule:close-all-registration-entities. $this->drushQueueCommands->closeAllRegistrationEntities(); // Assert that the registration entity was updated. $this->assertCount(1, \Drupal::entityTypeManager() ->getStorage('registration') ->loadByProperties(['field_sequence_is_open' => 0])); } } } namespace { if (!function_exists('dt')) { /** * Stub for dt(). * * @param string $message * The text. * @param array $replace * The replacement values. * * The text. */ function dt($message, array $replace = []): string { return strtr($message, $replace); } } if (!function_exists('drush_op')) { /** * Stub for drush_op. * * @param callable $callable * The function to call. */ function drush_op(callable $callable) { $args = func_get_args(); array_shift($args); return call_user_func_array($callable, $args); } } } |
I learned this via the migrate_tools project here.