You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 14:25:30