I have an artisan command (Laravel 10) that makes a change to the user table in the database. I can confirm that the change is happening with output from the command itself, and it looks like it’s actually working.
However, when I run the unit test, it does not see the updated database change.
Unit Test
public function test_promote_user_command_sets_user_as_super_admin()
{
$user = $this->createUser('guest');
$response = $this->artisan("user:promote {$user->email}")->assertSuccessful();
$role = Role::where('name', '=', 'Super Admin')->first();
$this->assertDatabaseHas('users', [
'id' => $user->id,
'role_id' => $role->id
]);
}
Artisan Command
class PromoteUserCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'user:promote {email}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Promote a user to super admin.';
/**
* Execute the console command.
*/
public function handle(): void
{
$role = Role::where('name', '=', 'Super Admin')->first();
$user = User::where('email', '=', $this->argument('email'))->first();
$user->role_id = $role->id;
$user->save();
}
}
When I run the test I get this output:
Failed asserting that a row in the table [users] matches the attributes {
"id": 1,
"role_id": 1
}.
Found similar results: [
{
"id": 1,
"role_id": null
}
].
However, if in the artisan command I run dd($user)
after the $user->save()
and then I run the test, I get this:
...
#attributes: array:9 [
"id" => 1
"name" => "Karelle Schamberger"
"email" => "[email protected]"
"email_verified_at" => "2023-03-04 18:22:36"
"password" => "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi"
"remember_token" => "Br992xsWw9"
"created_at" => "2023-03-04 18:22:36"
"updated_at" => "2023-03-04 18:22:36"
"role_id" => 1
]
#original: array:9 [
"id" => 1
"name" => "Karelle Schamberger"
"email" => "[email protected]"
"email_verified_at" => "2023-03-04 18:22:36"
"password" => "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi"
"remember_token" => "Br992xsWw9"
"created_at" => "2023-03-04 18:22:36"
"updated_at" => "2023-03-04 18:22:36"
"role_id" => 1
]
#changes: array:1 [
"role_id" => 1
]
...
So I know that the command is being run and that it is working properly. Why does it seem that this change is reverted before the test assertion happens?
Edited to add:
I also changed the command to do a DB lookup on the table after the $user->save()
and the DB result also shows the change is in the database…
So the DB changes is taking place within the artisan command, but either that change is reverted or some how the unit test is not seeing the latest DB status.
All of my other 94+ tests that do not involve the artisan command work just fine with database testing. It is only the artisan command that is having this issue.
3
Answers
I found the problem, but I do not know why this is a problem.
I had to change this line:
To this:
For some reason, trying to capture the output into a response variable made this act strange. To troubleshoot I used the enableQueryLog.
This showed nothing being changed (even though if I did a dd within the command it still worked...), and when I removed the
$response =
then the query returned the expected results.Can’t pinpoint exactly where your problem is, but there is some things that are not as intended.
You are calling the command with the value surrounded by brackets, which i think should not be there, as you would not include them in the
cli
. Secondly the second parameter orartisan()
can be the data values. Changing this into.Secondly you are actually not knowing if the command has crashed. Even if it seems simply a lot of small things can cause it to crash and your test would just continue, so also asserting the outcome of the command will guarantee it is executed correctly.
First of all, you do not use
user:promote whatever_value
, you useartisan("user:promote", ['email' => $user->email])
.Second, the issue is that
artisan
method, returns a class that you then call->assertSuccessful();
, this last method returns nothing, BUT it is using__destruct
, that means that, when nothing else is calling the class, it will execute the command and return the class, and then callassertXXXXX
.But because you are storing that in a variable, nothing is executed until the variable goes out of scope, when the test finishes, so the command is not run…
This is the
__destruct
code, so you can see it is literally executingrun()
, hence executing your command.See that
$this->artisan(...)
returns aPendingCommand
, the one I shared before (__destruct
).More PHP official info about it here. The important part to take away from it is:
Your code:
Correct code: