30 Days of Automated Testing:Using PHPUnit【D11】

Command Testing

WilliamP
4 min readJan 23, 2023

In modern Laravel web applications, commands are a common application, and Laravel also provides many convenient functions for testing them. Let’s introduce them to you below.

Commonly Used Command Testing Functions

artisan()

  • Signatureartisan($command, $parameters = [])
  • Explanation:This is probably the most important function in command testing! This function allows you to execute a command in the test code.

assertSuccessful()

  • SignatureassertSuccessful()
  • Explanation:This function needs to be used after $this->artisan(), it can verify whether the command was successfully executed and returns an exit code 0 (exit code 0 generally means the program has completed without errors or exceptions).

assertFailed()

  • SignatureassertFailed()
  • Explanation:This function needs to be followed by $this->artisan(), it can verify whether the command did not successfully complete, as long as the command does not respond with exit code 0, it will be treated as Failed.

expectsConfirmation()

  • SignatureexpectsConfirmation($question, $answer = 'no')
  • Explanation:This function needs to be used after $this->artisan(), it can verify whether there is an expected confirmation message output.

expectsQuestion()

  • SignatureexpectsQuestion($question, $answer)
  • Explanation:This function needs to be followed by $this->artisan(), it can verify whether there is an expected prompt message output.

expectsOutput

  • SignatureexpectsQuestion($output)
  • Explanation:This function needs to be followed after $this->artisan(), it can verify if there is an expected message output in the text.

Examples

The above introduction about command-related testing functions, below is an example to show the practical application for everyone!

app/Console/Commands/DeleteUser.php

<?php

namespace App\Console\Commands;

use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;

class DeleteUser extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'delete-user {userId}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Delete the user of given id';

/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
{
$userId = $this->argument('userId');

$user = User::find($userId);

if (empty($user)) {
$this->warn('User not found!');

return 1;
}

if (!$this->confirm('Are you sure to delete this user?')) {
return 1;
}

$logOptions = ['Console output', 'Log file'];

$logType = $this->choice(
'Please select log option:',
$logOptions
);

$user->delete();

$message = 'User deleted';

if ($logType === $logOptions[0]) {
$this->info($message);
} else {
Log::info($message, ['userId' => $userId]);
}

return 0;
}
}

In the above code, we implemented a command DeleteUser which is executed when we run php artisan delete-user {userId} in the command line. This command has 1 input parameter userId which is the ID of the user to be deleted. The functionality and behavior of this command are roughly as follows:

  • Attempt to retrieve the User data with the given userId, if there is no data, display an error and end the command, otherwise continue the process.
  • Output the text Are you sure you want to delete this user? to let the user confirm whether to continue with the delete command, and decide whether to end the command prematurely or continue the process based on the user's response.
  • If the user decides to continue the process, ask the user how they want to record the operation.
  • Delete the User data.
  • Perform the record keeping based on the user’s chosen method.
  • End the command and return an exit code 0.

tests/Feature/CommandTest.php

<?php

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class CommandTest extends TestCase
{
use RefreshDatabase;

public function testUserNotFound()
{
$this->artisan('delete-user',['userId' => 1])
->expectsOutput('User not found!')
->assertFailed();
}

public function testSuccessDeleteUser()
{
$user = User::factory()->create();
$userId = $user->id;

$this->artisan('delete-user', ['userId' => $userId])
->expectsConfirmation(
'Are you sure to delete this user?', 'yes'
)
->expectsQuestion('Please select log option:', 0)
->expectsOutput('User deleted')
->assertSuccessful();

$this->assertDatabaseCount('users', 0);
}

public function testCancelDeleteUser()
{
$user = User::factory()->create();
$userId = $user->id;

$this->artisan('delete-user', ['userId' => $userId])
->expectsConfirmation('Are you sure to delete this user?')
->assertSuccessful();

$this->assertDatabaseHas('users', [
'id' => $userId,
]);
}
}

The above test code consists of 3 test cases:

  • The first test case, testUserNotFound(), tests the behavior and output of the command when the given userId cannot be found in the database.
  • The second test case, testSuccessDeleteUser(), tests the behavior and output of the command when the given userId can be found in the database and the user inputs "yes" to the confirmation prompt.
  • The third test case, testCancelDeleteUser(), tests the behavior and output of the command when the given userId can be found in the database and the user inputs "no" to the confirmation prompt.

The above is an introduction to command testing, and I hope it is helpful to all readers.

Next, let’s talk about testing “failures”!

If you liked this article or found it helpful, feel free to give it some claps and follow the author!

Reference

Articles of This Series

--

--