Laravel中PUT更新资源时断言测试返回500而非预期200的问题
为何Laravel更新Todo的测试返回500状态码?
我是PHP新手,正在开发一个CRUD待办事项应用。借助Laravel的便捷性开发过程很顺利,但遇到了一个问题。
尝试通过以下端点更新"todo"数组:
Route::put("/todos/{user}/{id}", [TodoController::class, "update"])->middleware(["auth", "verified"])->name("todo.update");
该端点接收请求中的$user和$id参数,由update控制器处理:
public function update(Request $request, $user, $id) { $todo = Todo::findOrFail($id); if ($user->id != $todo->user_id) { return response()->json([ "error" => $todo->errors()->first(), ], 400); } $validator = Validator::make($request->all(), [ "text" => ["required", "string"], "user_id" => ["required", "integer", "string"], "isDone" => ["required", "integer", "boolean", "string"], ]); if ($validator->fails()) { return response()->json([ "error" => $validator->errors()->first(), ], 400); } $requestData = $request->only(["text", "isDone"]); return response()->json(Todo::edit($id, $requestData), 200); }
最终调用模型的edit方法:
public static function edit($id, $data) { return self::where("id", $id)->update($data); }
编写了TodoControllerTest.php来测试控制器,测试代码如下:
<?php namespace Tests\Feature; use App\Models\User; use App\Models\Todo; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class TodoControllerTest extends TestCase { use RefreshDatabase; public function test_authenticated_user_can_update_todo() { $user = User::factory()->create(); $todo = Todo::factory()->create(['user_id' => $user->id]); $updatedData = [ "text" => "updated todo text", "isDone" => true ]; $response = $this->actingAs($user)->put(route('todo.update', ['user' => $user->id, 'id' => $todo->id]), $updatedData); $response->assertStatus(200); $this->assertDatabaseHas("todos", $updatedData); } }
测试结果如下:
FAILED Tests\Feature\TodoControllerTest > authenticated user can update todo
Expected response status code [200] but received 500.
Failed asserting that 500 is identical to 200.
错误原因分析
- 路由参数处理错误:路由中
{user}参数传入的是用户ID(整数),但控制器方法直接将$user当作User模型实例调用$user->id,此时$user只是普通数值,并非模型对象,直接引发致命错误。 - 验证规则逻辑冲突:
user_id和isDone的验证规则同时包含integer、boolean、string,规则本身矛盾;且测试请求未传入user_id,本应触发验证失败,但因前面的参数错误已经抛出500,验证错误未被返回。 - 错误处理逻辑无效:
$todo->errors()->first()是错误调用,findOrFail返回的模型实例没有errors()方法,该方法属于验证器,此处会抛出方法不存在的错误。
修复方案
1. 修正路由与控制器参数绑定
利用Laravel隐式模型绑定,直接注入模型实例,避免手动处理ID:
// 路由保持不变,Laravel会自动解析参数为模型实例 Route::put("/todos/{user}/{id}", [TodoController::class, "update"])->middleware(["auth", "verified"])->name("todo.update"); // 控制器方法修改为注入User和Todo模型 public function update(Request $request, User $user, Todo $todo) { // 验证当前登录用户是否拥有该待办事项 if (auth()->id() !== $todo->user_id) { return response()->json([ "error" => "无权修改该待办事项" ], 403); } // 修正验证规则:移除冲突规则,user_id无需前端提交 $validator = Validator::make($request->all(), [ "text" => ["required", "string"], "isDone" => ["required", "boolean"], ]); if ($validator->fails()) { return response()->json([ "error" => $validator->errors()->first(), ], 400); } $requestData = $request->only(["text", "isDone"]); $todo->update($requestData); return response()->json($todo, 200); }
2. 修正模型edit方法(可选)
如果保留静态edit方法,确保返回更新后的模型而非受影响行数:
public static function edit($id, $data) { $todo = self::findOrFail($id); $todo->update($data); return $todo; }
3. 修正测试代码
无需在请求中传入user_id,且断言数据库时需结合todo的ID,避免数据冲突:
public function test_authenticated_user_can_update_todo() { $user = User::factory()->create(); $todo = Todo::factory()->create(['user_id' => $user->id]); $updatedData = [ "text" => "updated todo text", "isDone" => true ]; $response = $this->actingAs($user)->put(route('todo.update', ['user' => $user->id, 'id' => $todo->id]), $updatedData); $response->assertStatus(200); // 结合ID断言,确保是当前待办事项被更新 $this->assertDatabaseHas("todos", array_merge($updatedData, ['id' => $todo->id])); }
内容的提问来源于stack exchange,提问作者Esam Olwan
相关产品推荐
相关产品推荐

