Laravel 8功能测试中路由模型绑定失效问题
Laravel 8 功能测试中路由模型绑定失效问题解决
问题现象
对Laravel 8的Patch端点做功能测试时,控制器的update方法获取到的是新的Eloquent实例,而非预期的模型。测试代码中创建的Purchase模型无法通过路由模型绑定传递到PurchaseController,但该路由通过浏览器访问可正常工作。
相关代码
web.php
Route::name('admin.')->middleware(['admin'])->prefix('admin')->group(function () { Route::name('purchases.')->prefix('/purchases')->group(function () { Route::patch('/{purchase}/update', 'PurchaseController@update')->name('update'); }); });
PurchaseTest.php
class PurchaseTest extends TestCase { use RefreshDatabase; use WithoutMiddleware; public function test_purchase_can_be_updated() { $purchase = Purchase::create([ "item" => "tariqs item", "support" => "2022-09-09", "status" => PurchaseStatus::ACTIVE, "purchase_code" => "1234", "username" => "tariq", "user_id" => "1", ]); // $purchase模型无法通过该Patch请求传递到控制器 $response = $this->patch(route('admin.purchases.update', compact('purchase')), [ "item" => "tariqs item edit", "support" => "2022-09-09", "status" => PurchaseStatus::ACTIVE, "purchase_code" => "1234", "username" => "tariq", ]); $response->assertStatus(200); } }
PurchaseController.php
public function update(Request $request, Purchase $purchase) { dd($purchase, $request->all()); // 此处$purchase是新实例,而非预期模型 $input = $request->validate([ "item" => "required|string", "support" => "required|date_format:Y-m-d", "status" => "required|string", "purchase_code" => "required|string", "username" => "required|string", ]); if ($purchase->update($input)) { $purchases = Purchase::where('user_id', $purchase->user_id)->get(); return response()->json(['success' => 'true', 'msg' => __("Purchase code successfully updated."), 'embed' => view('admin.misc.purchase-list', compact('purchases'))->render()]); } throw ValidationException::withMessages(['error' => __('Something went wrong.')]); }
解决方案
1. 移除WithoutMiddleware trait(推荐)
测试类中使用的WithoutMiddleware会禁用所有中间件,包括路由模型绑定依赖的SubstituteBindings中间件——这个中间件负责将路由参数转换为对应的模型实例。移除该trait后,路由模型绑定就能正常工作:
class PurchaseTest extends TestCase { use RefreshDatabase; public function test_purchase_can_be_updated() { // ... 原有代码 } }
如果需要保留部分中间件禁用,可以手动指定要排除的中间件,而非全盘禁用。
2. 手动添加SubstituteBindings中间件
如果必须保留WithoutMiddleware,可以在测试的setUp方法中手动添加路由模型绑定所需的中间件:
class PurchaseTest extends TestCase { use RefreshDatabase; use WithoutMiddleware; protected function setUp(): void { parent::setUp(); // 添加入路由模型绑定中间件到web组 $this->app['router']->middlewareGroup('web', array_merge( $this->app['router']->middlewareGroups['web'], [\Illuminate\Routing\Middleware\SubstituteBindings::class] )); } public function test_purchase_can_be_updated() { // ... 原有代码 } }
3. 显式传递模型主键
虽然Laravel通常会自动从模型实例提取主键生成路由,但显式传递主键可以避免潜在的参数解析问题:
$response = $this->patch(route('admin.purchases.update', ['purchase' => $purchase->id]), [ // ... 请求参数 ]);
4. 验证模型路由键名称
如果Purchase模型自定义了getRouteKeyName方法(比如用purchase_code作为路由匹配键),需要确保测试中传递的参数与该键一致:
// 假设模型路由键是purchase_code $response = $this->patch(route('admin.purchases.update', ['purchase' => $purchase->purchase_code]), [ // ... 请求参数 ]);
额外验证
修复后可以添加数据库断言,确认模型确实被更新:
$this->assertDatabaseHas('purchases', [ 'id' => $purchase->id, 'item' => "tariqs item edit" ]);
内容的提问来源于stack exchange,提问作者Tariq Imtinan
相关产品推荐
相关产品推荐

