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

Laravel中MorphToMany关联在OrderProduct测试中异常求助

Laravel MorphToMany关联测试失败排查(OrderProduct模型)

问题背景

在Laravel项目中使用MorphToMany关联将Cost模型与Order、OrderProduct模型建立关联,相关代码如下:

迁移文件

Schema::create('costables', function (Blueprint $table) {
    $table->unsignedBigInteger('cost_id');
    $table->morphs('costable');

    $table->foreign('cost_id')
        ->references('id')->on('costs')
        ->onUpdate('cascade')
        ->onDelete('cascade');
});

Order模型(测试通过)

namespace Domain\Webshop\Models;

use Domain\Customer\Models\Address;
use Domain\Customer\Models\Member;
use Domain\Product\Models\Cost;
use Domain\Webshop\Database\Factories\OrderFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class Order extends Model
{
    protected $fillable = [
        'member_id',
        'shipping_address_id',
        'billing_address_id',
        'status',
    ];

    public static $rules = [
        'member_id' => 'required|integer',
        'shipping_address_id' => 'sometimes|integer',
        'billing_address_id' => 'nullable|integer',
        'status' => 'required|integer',
    ];

    protected $casts = [
        'member_id' => 'integer',
        'shipping_address_id' => 'integer',
        'billing_address_id' => 'integer',
    ];

    protected $hidden = ['created_at', 'updated_at', 'deleted_at'];

    protected static function newFactory(): OrderFactory
    {
        return OrderFactory::new();
    }

    public function member(): BelongsTo
    {
        return $this->belongsTo(Member::class, 'member_id');
    }

    public function shippingAddress(): BelongsTo
    {
        return $this->belongsTo(Address::class, 'shipping_address_id');
    }

    public function costs(): MorphToMany
    {
        return $this->morphToMany(Cost::class, 'costable');
    }
}

PHPUnit测试用例(Trait形式)

/** @test */
public function shouldHasCosts()
{
    $item = $this->model::factory()
        ->hasCosts(1)
        ->create();

    $this->assertInstanceOf(
        Cost::class,
        $item->first()->costs()->first(),
        "It should be defined a relation called costs on $this->model"
    );
}

测试用例对Order模型测试通过,但对OrderProduct模型测试失败,需排查问题原因。


排查与解决步骤

1. 确认OrderProduct模型已定义costs关联

这是最常见的遗漏点:必须在OrderProduct模型中添加与Order一致的costs关联方法,否则测试时无法找到关联关系:

namespace Domain\Webshop\Models;

use Domain\Product\Models\Cost;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class OrderProduct extends Model
{
    // ... 其他模型代码

    public function costs(): MorphToMany
    {
        return $this->morphToMany(Cost::class, 'costable');
    }
}

2. 检查OrderProductFactory是否支持hasCosts关联

测试中使用了->hasCosts(1)语法,需要确保OrderProductFactory中已定义该关联方法,或者使用Laravel原生的工厂关联语法:

  • 方法一:在OrderProductFactory中定义hasCosts方法
public function hasCosts(int $count)
{
    return $this->has(Cost::factory()->count($count), 'costs');
}
  • 方法二:直接修改测试代码使用原生has方法
$item = $this->model::factory()
    ->has(Cost::factory()->count(1), 'costs')
    ->create();

3. 验证costables表的costable_type字段值

Laravel的morphs字段会存储模型的完整类名,需确认OrderProduct的类名(包含命名空间)与costables表中存储的costable_type一致。可以在测试中临时添加打印语句排查:

dd($item->costable_type, get_class($this->model));

4. 确认OrderProduct模型的$table属性正确

如果OrderProduct对应的数据库表名不是Laravel默认的order_products,必须在模型中显式指定$table属性,否则关联时会找不到正确的表:

class OrderProduct extends Model
{
    protected $table = 'custom_order_products_table';
    // ... 其他代码
}

5. 检查测试类中$this->model的指向

确保使用该Trait的OrderProduct测试类中,$this->model被正确赋值为OrderProduct::class,避免指向错误的模型类。


内容的提问来源于stack exchange,提问作者Ferenc Bablena

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 08:22:49