Skip to content
Advertisement

Laravel 5 console (artisan) command unit tests

I am migrating my Laravel 4.2 app to 5.1 (starting with 5.0) and am a lot of trouble with my console command unit tests. I have artisan commands for which I need to test the produced console output, proper question/response handling and interactions with other services (using mocks). For all its merits, the Laravel doc is unfortunately silent with regards to testing console commands.

I finally found a way to create those tests, but it feels like a hack with those setLaravel and setApplication calls.

Is there a better way to do this? I wish I could add my mock instances to the Laravel IoC container and let it create the commands to test with everything properly set. I’m afraid my unit tests will break easily with newer Laravel versions.

Here’s my unit test:

Use statements:

use Mockery as m;
use AppConsoleCommandsAddClientCommand;
use SymfonyComponentConsoleTesterCommandTester;

Setup

public function setUp() {
    parent::setUp();

    $this->store = m::mock('AppServicesStore');

    $this->command = new AddClientCommand($this->store);

    // Taken from laravel/framework artisan command unit tests
    // (e.g. tests/Database/DatabaseMigrationRollbackCommandTest.php)
    $this->command->setLaravel($this->app->make('IlluminateContractsFoundationApplication'));

    // Required to provide input to command questions (provides command->getHelper())
    // Taken from ??? when I first built my command tests in Laravel 4.2
    $this->command->setApplication($this->app->make('SymfonyComponentConsoleApplication'));
}

Input provided as command arguments. Checks console output

public function testReadCommandOutput() {
    $commandTester = new CommandTester($this->command);

    $result = $commandTester->execute([
        '--client-name' => 'New Client',
    ]);

    $this->assertSame(0, $result);
    $templatePath = $this->testTemplate;

    // Check console output
    $this->assertEquals(1, preg_match('/^Client 'New Client' was added./m', $commandTester->getDisplay()));
}

Input provided by simulated keyboard keys

public function testAnswerQuestions() {
    $commandTester = new CommandTester($this->command);

    // Simulate keyboard input in console for new client
    $inputs = $this->command->getHelper('question');
    $inputs->setInputStream($this->getInputStream("New Clientn"));
    $result = $commandTester->execute([]);

    $this->assertSame(0, $result);
    $templatePath = $this->testTemplate;

    // Check console output
    $this->assertEquals(1, preg_match('/^Client 'New Client' was added./m', $commandTester->getDisplay()));
}

protected function getInputStream($input) {
    $stream = fopen('php://memory', 'r+', false);
    fputs($stream, $input);
    rewind($stream);
    return $stream;
}

updates

  1. This doesn’t work in Laravel 5.1 #11946

Advertisement

Answer

I have done this before as follows – my console command returns a json response:

public function getConsoleResponse()
{
    $kernel = $this->app->make(IlluminateContractsConsoleKernel::class);
    $status = $kernel->handle(
        $input = new SymfonyComponentConsoleInputArrayInput([
            'command' => 'test:command', // put your command name here
        ]),
        $output = new SymfonyComponentConsoleOutputBufferedOutput
    );

    return json_decode($output->fetch(), true);
}

So if you want to put this in it’s own command tester class, or as a function within TestCase etc… up to you.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement