Laravel 10 Unit Test does not see DB change from Artisan Command

Mas*_*erZ 3 php testing tdd laravel laravel-artisan

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
    ]);
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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
    }
].
Run Code Online (Sandbox Code Playgroud)

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" => "urice@example.org"
    "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" => "urice@example.org"
    "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
  ]
...
Run Code Online (Sandbox Code Playgroud)

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.

mat*_*iti 5

首先,你不使用user:promote whatever_value,你使用artisan("user:promote", ['email' => $user->email])

其次,问题是该artisan方法返回一个您随后调用的类->assertSuccessful();,最后一个方法什么都不返回,但它正在使用__destruct,这意味着,当没有其他东西调用该类时,它将执行该命令并返回该类,并且然后打电话assertXXXXX

但是因为您将其存储在变量中,所以在变量超出范围、测试完成时不会执行任何操作,因此不会运行该命令...

这是__destruct代码,因此您可以看到它实际上正在执行run(),从而执行您的命令。

请参阅$this->artisan(...)返回 a PendingCommand,即我之前共享的 ( __destruct)。

更多关于它的 PHP 官方信息请参见此处。其中重要的部分是:

一旦没有其他对特定对象的引用,或者在关闭序列期间以任何顺序,就会调用析构函数方法。


你的代码:

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
}
Run Code Online (Sandbox Code Playgroud)

正确代码:

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
    ]);
}
Run Code Online (Sandbox Code Playgroud)