我有一个 artisan 命令 (Laravel 10) 可以更改数据库中的用户表。我可以确认命令本身的输出正在发生更改,看起来它确实在工作。
但是,当我运行单元测试时,它没有看到更新的数据库发生变化。
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();
}
}
当我运行测试时,我得到这个输出:
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
}
].
但是,如果在 artisan 命令中我在
dd($user)
之后运行 $user->save()
然后我运行测试,我得到这个:
...
#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
]
...
所以我知道命令正在运行并且它正在正常工作。为什么在测试断言发生之前似乎已恢复此更改?
我还更改了命令以在
$user->save()
之后对表进行数据库查找,并且数据库结果还显示更改在数据库中...
因此数据库更改发生在 artisan 命令中,但要么该更改被还原,要么单元测试无法看到最新的数据库状态。
我所有其他不涉及 artisan 命令的 94+ 测试都可以很好地用于数据库测试。只有工匠命令有这个问题。
首先,你不用
user:promote whatever_value
,你用artisan("user:promote", ['email' => $user->email])
.
第二,问题是
artisan
方法,返回一个类,然后调用 ->assertSuccessful();
,最后一个方法不返回任何内容,但它正在使用 __destruct
,这意味着,当没有其他任何东西调用该类时,它将执行命令并返回类,然后调用assertXXXXX
.
但是因为您将其存储在变量中,所以在变量超出范围之前不会执行任何操作,当测试完成时,因此命令不会运行...
__destruct
代码,所以你可以看到它实际上是在执行run()
,因此执行你的命令。
$this->artisan(...)
返回一个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
}
正确代码:
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
]);
}
无法准确指出您的问题出在哪里,但有些事情并不符合预期。
你调用命令时用方括号括起来的值,我认为不应该有,因为你不会将它们包含在
cli
中。其次,第二个参数或artisan()
可以是数据值。把这个改成。
$this->artisan("user:promote", ['email' => $user->email]);
其次,您实际上并不知道命令是否已崩溃。即使看起来只是很多小事情都会导致它崩溃并且您的测试只会继续,所以断言命令的结果将保证它被正确执行。
$this->artisan("user:promote", ['email' => $user->email])
->assertSuccessful();
我发现了问题,但我不知道为什么这是个问题。
我不得不改变这一行:
$response = $this->artisan("user:promote {$user->email}")->assertSuccessful();
对此:
$this->artisan("user:promote {$user->email}")->assertSuccessful();
出于某种原因,试图将输出捕获到响应变量中会使这种行为变得奇怪。为了排除故障,我使用了 enableQueryLog。
\DB::enableQueryLog();
$response = $this->artisan("user:promote {$user->email}")->assertSuccessful();
dd(\DB::getQueryLog());
这表明没有任何改变(即使我在命令中执行了 dd 它仍然有效......),当我删除
$response =
然后查询返回预期结果。