Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 App\Console\Commands\AddClientCommand;
use Symfony\Component\Console\Tester\CommandTester;

Setup

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

    $this->store = m::mock('App\Services\Store');

    $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('Illuminate\Contracts\Foundation\Application'));

    // 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('Symfony\Component\Console\Application'));
}

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 Client\n"));
    $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
like image 564
bernie Avatar asked Jan 15 '16 15:01

bernie


2 Answers

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

public function getConsoleResponse()
{
    $kernel = $this->app->make(Illuminate\Contracts\Console\Kernel::class);
    $status = $kernel->handle(
        $input = new Symfony\Component\Console\Input\ArrayInput([
            'command' => 'test:command', // put your command name here
        ]),
        $output = new Symfony\Component\Console\Output\BufferedOutput
    );

    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.

like image 62
Gravy Avatar answered Nov 16 '22 03:11

Gravy


use Illuminate\Support\Facades\Artisan;
use Symfony\Component\Console\Output\BufferedOutput;

$output = new BufferedOutput();

Artisan::call('passport:client', [
    '--password' => true,
    '--name' => 'Temp Client',
    '--no-interaction' => true,
], $output);

$stringOutput = $output->fetch();


like image 21
James Bond Avatar answered Nov 16 '22 02:11

James Bond