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.
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
]);
}
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?
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.
First of all, you do not use user:promote whatever_value
, you use artisan("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 call assertXXXXX
.
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 executing run()
, hence executing your command.
See that $this->artisan(...)
returns a PendingCommand
, the one I shared before (__destruct
).
More PHP official info about it here. The important part to take away from it is:
The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence.
Your code:
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
]);
// Now the artisan command is executed
}
Correct code:
public function test_promote_user_command_sets_user_as_super_admin()
{
$user = $this->createUser('guest');
$this->artisan("user:promote {$user->email}")->assertSuccessful();
// Now the artisan command is executed
$role = Role::where('name', '=', 'Super Admin')->first();
$this->assertDatabaseHas('users', [
'id' => $user->id,
'role_id' => $role->id
]);
}