Testing Console Commands

Console commands are a regular feature in many Craft modules and plugins as well as the Craft core itself. They are an extremely useful developer tool to cut straight through to application functionality without the fuss of a UI. They’re fundamentally different to test than web applications, and Craft offers native support for testing them.

The way you can test your console commands with Craft is inspired by the excellent work done over at Laravel by @themsaid. If you are familiar with testing Laravel applications this will feel quite familiar.

# How it works

You can test console controllers by creating a unit test and setting it up in a specific way (see the guide below for a practical example). Within this unit test Craft makes available the following method $this->consoleCommand(). This method creates a craft\test\console\CommandTest object which is set up with a fluid interface. This allows you to build out your console command’s user-based output methods (i.e. what stdOut, stderr, prompt, confirm, etc.) must be called when triggering your console command and according to what specification.

Please ensure you call $this->outputCommand() in your console commands and not craft\helpers\Console::outputCommand(). This static method will not be taken into account as it is currently not possible to mock static methods, something required for the CommandTest class to work.

# Step 1: Extend a specific class

Your unit test needs to extend craft\test\console\ConsoleTest.

<?php

namespace crafttests\unit\console;

use \craft\test\console\ConsoleTest;

class MyConsoleTest extends ConsoleTest
{
}

# Step 2: Create a test

Create a test like you would in any other unit test.

<?php

namespace crafttests\unit\console;

use \craft\test\console\ConsoleTest;

class MyConsoleTest extends ConsoleTest
{
    public function testSomething()
    {
    }
}

# Step 3: Invoke the consoleCommand method

Invoke the consoleCommand method as follows.


public function testSomething()
{
    $this->consoleCommand('test-controller/test-action');
}

# Step 4: Add steps and run the command

Because the consoleCommand returns a fluid interface you can add as many methods (see options below) in order to specify what 'user journey' your console command will follow.

public function testSomething()
{
    $this->consoleCommand('test-controller/test-action')
        ->stdOut('This output must be given')
        ->stdOut('Followed by this one')
        ->prompt('The user must then input something', 'This will be returned in the controller action (your console command)', 'the $default value')
        ->exitCode(ExitCode::OK)
        ->run();
}

You must end the chain with a ->exitCode($value) call to specific what exit code must be returned. This call must then be followed by a ->run() call, which runs the command.

The commands will be checked in the order you define them. So if your console command is structured as follows:

public function actionSomething() {
    $this->stdOut('first');
    $this->stdOut('second');
}

Don’t setup your method call as follows:

$this->consoleCommand('test-controller/test-action')
        ->stdOut('second')
        ->stdOut('first')
        ->exitCode(ExitCode::OK)
        ->run();

As this will fail.

If you want to ignore all stdOut calls you can pass false as the third parameter of the consoleCommand() call. You will then not have to define your stdOut calls when calling $this->consoleCommand() and Craft will ignore then when checking what your console command returns to the user.

# Methods

# stdOut

  • string $desiredOutput: The string that should be output by your console command

If your console command calls $this->stdOut() you should test that this method is correctly called using the stdOut method. The value you pass in will be checked against what your console command passes in when calling $this->stdOut().

# stderr

  • string $desiredOutput: The error string that should be output by your console command

Exactly the same principal as stdOut above - except for the $this->stderr() method.

# prompt

  • string $prompt: What prompt your console command should invoke for the user
  • $returnValue: What value should be returned by $this->prompt() when it is called in your console command.
  • array $options = []: The options your console command should pass into the $this->prompt() call.

If your console command calls $this->prompt() this method ensures that you can test how this method is called and what user input is returned (as there is no actual user in testing).

# confirm

  • string $message: The message that your console command should ask the user to confirm
  • $returnValue: The value that is returned to your console command
  • bool $default = false: The $default value that is passed into the $this->confirm() method by your console command

If your console command calls $this->confirm() this method ensures that you can test how this method is called and what user input is returned (as there is no actual user in testing).

# select

  • string $prompt: The prompt that should be asked of your user by your console command when calling $this->select()
  • $returnValue: The value that is returned to your console command by $this->select()
  • $options = []: The options passed into $this->select() by your console command

# outputCommand

  • string $command: The command to output when calling $this->outputCommand()
  • bool $withScriptName = true: What value should be passed as second argument when your console command calls $this->outputCommand()

If your console command calls $this->outputCommand() this method ensures that you can test how this method is called and what is output to the user.

Please ensure you call $this->outputCommand() in your console commands and not craft\helpers\Console::outputCommand(). This static method will not be taken into account as it is currently not possible to mock static methods, something required for the CommandTest class to work.