Laravel Pest测试因MariaDB自增ID未重置导致断言失败的解决方法
我编写了一个Laravel Pest测试,用于验证提交数据创建客户端后控制器的重定向逻辑,测试代码如下:
it('redirects to the clients show page', function () { $user = User::factory()->asAdmin()->create(); auth()->login($user); post(route('clients.store'), [ 'name' => 'Test Client', 'description' => 'Test Description', 'poc' => 'Test POC', 'poc_email' => 'Test POC Email', 'image_path' => 'Test Image Path', ])->assertRedirect(route('clients.show', Client::first())); });
单独运行该测试时一切正常,但运行整个测试套件(之前有多个创建并删除客户端的测试)时,测试失败。原因是每次测试后MariaDB的自增ID未重置,报错信息如下:
Failed asserting that two strings are equal. Expected :'http://localhost/clients/4' Actual :'http://localhost/clients/1' <Click to see difference> at vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:311 at vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:178 at tests/Feature/Controllers/ClientController/StoreTest.php:87 at vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php:177
测试中使用了refresh database trait,出于性能考虑,测试后自增索引不重置是预期行为。环境信息:DB为MariaDB 11.2,PHP 8.3.1,Laravel 10.40。
奇怪的是,新创建客户端的重定向路由指向正确的ID '1',但Client::first()返回的客户端ID为'4',且该测试套件在SQLite下运行正常。
请问如何修改测试,使其不依赖客户端的自增ID来完成验证?
方法1:通过唯一字段定位刚创建的客户端
不要依赖Client::first(),而是通过请求中提交的唯一字段(比如name)查询刚创建的客户端,避免受自增ID序列影响:
it('redirects to the clients show page', function () { $user = User::factory()->asAdmin()->create(); auth()->login($user); $clientData = [ 'name' => 'Test Client', 'description' => 'Test Description', 'poc' => 'Test POC', 'poc_email' => 'test@poc.example.com', 'image_path' => 'Test Image Path', ]; $response = post(route('clients.store'), $clientData); // 通过唯一的name字段查询目标客户端 $client = Client::where('name', $clientData['name'])->first(); $response->assertRedirect(route('clients.show', $client)); });
注意:确保name字段在业务中是唯一的,或者改用其他唯一标识字段;同时修正poc_email为合法格式,避免触发验证错误导致测试失败。
方法2:取最新创建的客户端
利用latest()方法按创建时间倒序查询,直接获取刚创建的客户端,不受自增ID顺序影响:
it('redirects to the clients show page', function () { $user = User::factory()->asAdmin()->create(); auth()->login($user); $response = post(route('clients.store'), [ 'name' => 'Test Client', 'description' => 'Test Description', 'poc' => 'Test POC', 'poc_email' => 'test@poc.example.com', 'image_path' => 'Test Image Path', ]); // 取最新创建的客户端 $client = Client::latest()->first(); $response->assertRedirectToRoute('clients.show', $client); });
这里使用assertRedirectToRoute方法(Laravel 8+支持),可以直接传入路由名称和模型参数,比手动拼接URL更简洁可靠。
方法3:用正则验证重定向URL格式
如果只需要验证重定向到客户端详情页,不需要确认具体客户端,可以用正则表达式匹配URL格式:
it('redirects to the clients show page', function () { $user = User::factory()->asAdmin()->create(); auth()->login($user); post(route('clients.store'), [ 'name' => 'Test Client', 'description' => 'Test Description', 'poc' => 'Test POC', 'poc_email' => 'test@poc.example.com', 'image_path' => 'Test Image Path', ])->assertRedirectMatches('/^\/clients\/\d+$/'); });
这种方式不需要查询数据库,但无法验证是否重定向到刚创建的客户端,适合只校验路由类型的场景。
额外说明
出现Client::first()返回旧ID的原因:refresh database trait通过回滚事务清理测试数据,但MariaDB不会回滚自增序列;而SQLite在事务回滚后会重置自增ID,因此SQLite环境下测试正常。
内容的提问来源于stack exchange,提问作者FMK

