Writing Better Test CLI Commands with Laravel and Artisan

Laravel’s Artisan CLI comes with a plethora of useful commands and functionality out of the box. Unfortunately, there’s one thing it doesn’t come with: an easy way to run different types of tests.

On a recent project, our team had a desire to write Artisan commands that could run our tests, so we had one consistent CLI interface. We also wanted to make sure we had commands to run not only our entire test suite but also individual types of tests.

Below, I’ll share the Artisan commands we created to help us achieve this goal.

This post assumes that your tests make use of PHPUnit’s @group doc tag at the top of each file. If they don’t, you’ll want to add that to each of your tests like so:

/** 
 * @group unit 
 */
public class YourTestClass extends TestCase 
{   
  // ... your test cases here
}

This code tells PHPUnit (or Dusk, for browser testing) to run this test whenever it gets called with the --group=unit command. You’ll want to adjust @group unit to be whatever group your test actually is, like @group feature. In our code, we use unit, feature, and browser tests, so all of our tests have one of those three groups assigned to it. You’ll want to do the same for your tests.

Unit Tests

Now that all of your tests are properly grouped, it’s time to start writing commands to run them. For unit tests, run this command in your terminal to have Laravel scaffold a new Artisan command for you:

$ php artisan command:make TestUnit

Next, set the description of the command and the signature. I recommend using test:unit for the signature, but you could set it to something different.

Once you’ve got the signature and description set, it’s time to create the functionality of your command. Place the following code in your handle() function:

public function handle() 
{  
  $command = 'vendor/bin/phpunit --group=unit';  
  $process = Process::fromShellCommandline($command);   

  $this->info('');  
  $this->info('---------------------------------------');  
  $this->info('RUNNING UNIT TESTS');  
  $this->info('---------------------------------------');   

  $process->setTty(true);   

  $process->run(function ($type, $buffer) {    
    $this->info($buffer);  
  });   

  $this->comment('');  
  $this->comment('FINISHED RUNNING UNIT TESTS');
}

Let’s go through what’s happening here:

  1. The first line sets a variable named $command to contain the actual command we want to run.
  2. Then, we’re queueing up another shell process to run the command. Artisan doesn’t provide a way for you to execute commands other than what the user entered by default, so we have to use the built-in process library.
  3. After creating a new process, there are a few lines of text output to help break up our results visually. Feel free to remove or change that if desired.

  4. Then we call setTty(true) on the process to enable colored text, which is desirable since PHPUnit does color its output.
  5. Finally, we actually run the process. We pass in a closure, so we can print output from our test process as it comes in. If you don’t do this, you’ll have to manually collect it all after the process resolves.

It’s a pretty simple command overall. Now, if you run php artisan test:unit, you should see your unit tests run. We’re off to a good start!

Feature Tests

Our feature tests also use PHPUnit as the test runner, so most of what we write here will look almost identical to what we wrote for our unit tests.

First, just like before, run this in your terminal to create a new command:

$ php artisan command:make TestFeature

Go through and adjust the signature and description of your new command. Once you’ve done that, make your handle() function look like this:

public function handle() 
{  
  $command = 'vendor/bin/phpunit --group=feature';  
  $process = Process::fromShellCommandline($command);
   
  $this->info('');  
  $this->info('---------------------------------------');  
  $this->info('RUNNING FEATURE TESTS');  
  $this->info('---------------------------------------');
   
  $process->setTty(true);   

  $process->run(function ($type, $buffer) {    
    $this->info($buffer);  
  });   

  $this->comment('');  
  $this->comment('FINISHED FEATURE TESTS');
}

This function is basically identical to the one we wrote for our unit tests. The main difference is that we’re now calling PHPUnit with the feature group rather than the unit. Try running php artisan test:feature to see your new command at work.

Browser Tests

As before, let’s start off by telling Artisan to make a test scaffold for us:

$ php artisan command:make TestBrowser

Once you’ve changed the signature and description of your test command, you’ll want to make your handle()look like this:

public function handle() 
{  
  $command = 'php artisan dusk';  
  $process = Process::fromShellCommandline($command);
   
  $this->info('');  
  $this->info('---------------------------------------');  
  $this->info('RUNNING BROWSER TESTS');  
  $this->info('---------------------------------------');
   
  $process->setTty(true);   

  $process->run(function ($type, $buffer) {    
    $this->info($buffer);  
  });   

  $this->comment('');  
  $this->comment('FINISHED FEATURE BROWSER TESTS');
}

You’ll notice one big difference here. Instead of running phpunit, we’re running php artisan dusk. This is the built-in Laravel command for running all Dusk tests; we don’t end up making use of the @group feature. You can still use the group argument when you run Dusk (if you had different types of browser or acceptance tests, for example), but we won’t use it for our simple use case.

Putting it All Together

We now have commands that can run all of our individual test types! There’s just one thing that’s missing. What about when we want to run our entire test suite? It’s easy enough to run all three commands, but it would be more convenient to have something dedicated to running all known tests. To achieve that, we’re going to create one more command. Just as before, you’ll want to create this new overall test command by running this in your terminal:

$ php artisan command:make Test

In your new command class, set the signature to just be test. This completes the pattern we’ve been following with our other commands: you can enter test:<test-type> to run a specific type of test or just enter test to run the entire suite.

To actually enable our command to run all of our tests, we’re going to use an Artisan command’s built-in call() method, which allows us to run another command from within our handle() function. You’ll want your code to look like this:

public function handle()
{  
  $this->comment('Running all tests...');  
  $this->comment('----------------------------------');   

  $this->call('test:unit');  
  $this->call('test:feature');  
  $this->call('test:browser');   

  $this->comment('----------------------------------');  
  $this->comment('');  
  $this->comment('All tests complete!');
}

We’re taking advantage of the fact that we already have commands for our individual test types and calling those commands instead of rewriting the same logic. This means that if we ever need to change the testing library we use for one or more test types, we only need to change the call within that test type’s specific command. Our overall test command will still work.

Conclusion

You should now have a robust set of commands from Artisan to run all of your tests — either when developing locally or as part of the CI you’re using.

I hope that these commands mak your test workflow more efficient. Please share any other useful Artisan commands you’ve made in the comments.