0

Calling Artisan from code — Artisan::call()

Intermediate5 min read·lv-25-005

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
<?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();
    }
}