Calling Artisan from code — Artisan::call()
Concept
Testing Artisan commands uses the artisan() method in feature tests. Laravel provides assertions for command output, exit codes, and interactive prompts.
$this->artisan(string $command, array $params = []): Returns a PendingCommand with assertion methods. If no assertions are made, the command runs and the test passes if no exceptions are thrown.
Exit code assertions:
->assertExitCode(int $code): Check the exit code (0 = success, 1 = failure).->assertSuccessful(): Exit code is 0.->assertFailed(): Exit code is not 0.
Output assertions:
->assertOutputContains(string $text): Output includes text.->assertOutputMissing(string $text): Output does NOT include text.->assertSeeInOutput(string $text): Alias for contains.
Interactive input: Chain ->expectsQuestion(string $question, string $answer) for each expected prompt. Order matters — prompts are answered in sequence.
->expectsTable(array $headers, array $rows): Assert a table is output with specific headers and rows.
->expectsOutput(string $text): Assert exact output text. More strict than assertOutputContains.
Combining with Fake facades: Mock Mail::fake(), Event::fake(), Queue::fake() before calling artisan() to verify the command dispatched the right things.
Code Example
<?php
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Queue;
uses(RefreshDatabase::class);
test('report:weekly sends report to all users', function() {
Mail::fake();
Queue::fake();
$users = User::factory()->count(3)->create(['receives_weekly_report' => true]);
$this->artisan('report:weekly')
->assertSuccessful();
Mail::assertSentCount(3);
});
test('report:weekly sends to specific user by id', function() {
Mail::fake();
$user = User::factory()->create(['receives_weekly_report' => true]);
$this->artisan('report:weekly', ['user' => $user->id])
->assertSuccessful()
->assertOutputContains("Sending reports to 1 user");
});
test('report:weekly fails when user not found', function() {
$this->artisan('report:weekly', ['user' => 99999])
->assertFailed()
->assertOutputContains('No users found');
});
test('import command asks for confirmation in production', function() {
User::factory()->count(5)->create();
$this->artisan('users:import', ['file' => 'test.csv'])
->expectsQuestion('You are running this in PRODUCTION. Continue?', 'yes')
->expectsQuestion('Send welcome emails to imported users?', 'no')
->assertSuccessful();
});
test('interactive choice command', function() {
$this->artisan('backup:run')
->expectsQuestion('Which database?', 'production')
->expectsChoice('Select compression', 'gzip', ['none', 'gzip', 'bzip2'])
->expectsOutput('Backup complete!')
->assertExitCode(0);
});
// PHPUnit style
class SendWeeklyReportCommandTest extends TestCase
{
use RefreshDatabase;
public function test_command_runs_successfully(): void
{
$user = User::factory()->create();
$this->artisan('report:weekly')->assertSuccessful();
}
}