Mohamed Said's Journal

Web Developer, cyclist, and freediver.

Building and testing Artisan Commands

Posted on April, 9 2016 in Code

Building and testing Artisan Commands
Photo by Alex wong on Unsplash

While building the Laravel Langman package I was facing some difficulties trying to figure out how to test a console command that interacts with the user via questions, most of the tutorials and blog posts I found online were just calling the command and passing arguments/options using the `artisan()` method that comes with laravel's default test suite, however I didn't stumble upon a tutorial that covers user interaction with the console after calling the command.

In this post I'm going to share how I tested such type of commands, but let's first see how powerful artisan commands can be.

Building a console command

First we'll need to create a new command by calling:

php artisan make:console ConfigManager --command=config:manage

This will create a new class in `app/Console/Commands` with the following content:

class ConfigManager extends Command
  protected $signature = 'config:manage';

  protected $description = 'Command description';

  public function __construct()

  public function handle()

Our command will be used to manage laravel's configuration files, using this command we'll be able to read config files as well as update/add keys.

For us to be able to use the command we're going to make sure we have the right setup for the signature, laravel introduces an easy and simple way of defining a command signature, here are examples of the different cases:

command:name {file} // Required Argument
command:name {file?} // Optional Argument
command:name {file=app} // Optional Argument with default value
command:name {file} {--no-creation} // An option with true or false
command:name {file} {--key=} // An option that requires a value
command:name {file} {--key=default} // Accepts a value with a default

For the ConfigManager command we only need an optional argument for the file name, so let's set the `$signature` to the following:

protected $signature = 'config:manage {file?}';

It's also a good idea to set a description for the command:

protected $description = 'Manage application config files.';

Here's the full content of our command with description of each part of the code.

By reading the code you'll notice there are some interesting methods being called, such as `ask()`, `table()`, `confirm()`, `choice()`, etc...

These methods are part of the powerful Artisan Console provided by laravel, which is built on top of the Symfony Console component, let's discover some of the output methods.

// Print a text output with a predefined style.

// Print a raw text output.

// You may style your output like this:
$this->line('<fg=yellow;bg=magenta>Yellow text with a Magenta background.</> and a <options=bold>Bold Text</>.');

There's also a table component:

 ['Key', 'Value'], 
  ['', 'homestead'], 
  ['database.username' => 'forge']

The first argument is an array of table headers, the second argument is an array of table rows, each item is an array itself with a value for each column.


Here's the most interesting part, Artisan Commands can ask the user to provided specific pieces of information using a predefined methods that cover all the use cases an application might need, let's take a look at what's available:

// Asking a yes/no question with a default value 'false', the value
// provided by the user is saved into a variable that can be
// used later to decide the flow.
$canDelete = $this->confirm('Are you sure you want to delete?', false);

// Asking for a string value, if no value provided the default one 
// `themsaid` will be taken. In case no default value was
// provided the output of as() will be 'null'.
$username = $this->ask('Provide a username', 'themsaid');

// Asking for a string value and provide some auto-completion, the
// user can still provide any value he wants.
$this->anticipate('Favourite color', ['green', 'yellow', 'blue']);

// Asking for a string value and provide some auto-completion, the
// user MUST select from the provided choises.
$this->choice('Favourite character', ['Jon', 'Arya', 'Daenerys']);

// Asking for a string but never show the user input in the console.
$username = $this->secret('Provide a password');

Go ahead and playaround with these methods and see the possibilities they provide.

Testing a Console Command

Laravel 5.7 introduces a better aproach for testing artisan commands. Continue reading though if you want a way to mock your command and do different assertions on it.

Let's create a new test class:

php artisan make:test CommandTest

Inside our file we'll first write what we want to test in plain text:

class CommandTest extends TestCase
  public function testItErrorsIfFileNotFound()
    // Run config:manage not_found
    // Receive "The provided config file was not found!"

  public function testItAsksForFileIfNotProvidedAsArgument()
    // Run config:manage
    // Get asked to select a file from existing list

  public function testItDisplaysATableOfFileContent()
    // Run config:manage file
    // See correct records in table

  public function testItCreatesNewFileWhenNeeded()
    // Run config:manage file
    // Get asked "Would you like to create a new file?"
    // Answer yes
    // Get asked to provide file name "File name [ex: facebook]"
    // Provide a new file name
    // Get message "file was created successfully"
    // File created

  public function testItErrorsWhileCreatingAnExistingFile()
    // Run config:manage file
    // Get asked "Would you like to create a new file?"
    // Answer yes
    // Get asked to provide file name "File name [ex: facebook]"
    // Provide and existing file
    // Get error "File already exists!"
    // File is never touched

  public function testItPrintsAMessageAndExitsIfNoFileNeeded()
    // Run config:manage file
    // Get asked "Would you like to create a new file?"
    // Answer no
    // Get message "No file was created!"

To be able to properly test the command we'll need to create a temporary directory acting as the `config` directory. We'll also need to resolve our own instance of `Illuminate\Contracts\Console\Kernel` to be able to add a new method, we will talk about the new method shortly when we discuss our flow of testing the command, for now we'll just add these methods:

Update 2016-04-21
Starting Laravel `5.2.30` you don't need to register a custom Kernel instance as the registerCommand() method is now included in the core by default, you may skip that step.

protected function setUp()
  $this->app->singleton('Illuminate\Contracts\Console\Kernel', TestKernel::class);


public function tearDown()

  exec('rm -rf '.__DIR__.'/CommandTestTemp');

At the end of the Test class we'll add a new class with 1 method:

class TestKernel extends \Illuminate\Foundation\Console\Kernel
  public function registerCommand($command)

Using this methods we'll be able to register the command to Console\Kernel when we need, later when the command is called, the instance we pass using this method will be the one used.

Our first test case

public function testItErrorsIfFileNotFound()
  // First we register a partial mock of the command, in this test case
  // we only need to mock the error() method and leave the rest
  // of the methods as is.
  $command = m::mock('\App\Console\Commands\ConfigManager[error]',[new \Illuminate\Filesystem\Filesystem()]);

  // We expect the method to be called with a specific string indicating
  // that the config file we're trying to read doesn't exist.
  $command->shouldReceive('error')->once()->with('The provided config file was not found!');

  // Now we register our mocked command instance in Console Kernel.

  // Calling the command will run the mocked version of the command.
  // Notice how we pass the "file" command argument, that's how
  // arguments and options are passed to the artisan() test
  // helper method. Also we added '--no-interaction' to
  // prevent the application from expecting an actual
  // user input.
  $this->artisan('config:manage', ['file' => 'not_found', '--no-interaction' => true]);

So we create a partial mock from the command, mocking only the methods that indicate a console interaction we are concerned about, in this case it's the `error()` method, we add an expectation that this method will be called with the first argument containing a string "The provided config file was not found!".

Now the command will try to look for the a file called "not_found" which does not exist causing the `error()` method to be called:

if (! in_array($file, $configFiles)) {
  return $this->error('The provided config file was not found!');

Using the same principle we can test a user interaction with the console, let's look at our second test:

public function testItAsksForFileIfNotProvidedAsArgument()
  // We add two dummy files in the temporary test directory

  // We pass the path to the test directory as the second argument
  // of the constructor of the mocked command.
  $command = m::mock(
    [new \Illuminate\Filesystem\Filesystem(), __DIR__.'/CommandTestTemp']

  // We expect the choice method to be called asking us to select one
  // of the two files existing in the config path.
  		 ->with('Please select a file', ['one', 'two'])

  // Now we call the command and don't provide a "file" argument.
  $this->artisan('config:manage', ['--no-interaction' => true]);

So we mock the command, register the mocked version in Kernel, add our expectations for method calls, and pretend the user response in the form of return values.

I personally love building console commands whenever possible, I prefer dealing with the console rather than views, so if the task I want to accomplish is to be run by only me or someone who has access to the console I just write a command instead of having to build views, controllers, etc...

By Mohamed Said

I work at laravelphp on weekdays and practise running, cycling, & freediving on weekends. Find me on Twitter, GitHub, and Strava.

Built Using Wink