Laravel PHPUnit中如何Mock模型多层关联的属性返回值
with()是Eloquent ORM查询阶段的预加载方法,仅对模型查询构造器生效。你通过app(Check::class)拿到的是空白Check模型实例,没有执行数据库查询,无法通过with()挂载关联。
直接实例化的模型默认关联属性为null,会命中业务代码中if (!$user = $check->user)的判断直接返回false,走不到后续读取some_id的逻辑。
不需要在Mock逻辑中调用with(),直接构建好带完整关联链的模型实例,传入andReturn()即可,两种常用实现:
方案1:手动设置关联(无额外依赖,逻辑直观)
从最内层关联开始逐层实例化模型,通过setRelation()方法挂载关联,Eloquent访问关联时如果检测到关联已被设置,会直接返回对应实例,不会触发数据库查询:
$app->bind(Service::class, function () { return $this->mock(Service::class, function(MockInterface $mock) { $mock->makePartial(); // 构建最内层verification模型,设置目标属性 $verification = new Verification(); $verification->some_id = 123; // 替换为测试需要的具体值 // 构建user模型,挂载verification关联 $user = new User(); $user->setRelation('verification', $verification); // 构建check模型,挂载user关联 $check = new Check(); $check->setRelation('user', $user); $mock->shouldReceive('getCheck')->andReturn($check); }); });
注意:不要直接通过
$user->verification = $verification的方式赋值,该写法会把verification识别为模型表字段而非关联,必须使用setRelation()方法设置关联属性,传入的关联名要和模型中定义的关联方法名完全一致。
方案2:通过模型工厂生成(项目已配置模型工厂时推荐)
如果项目已经为对应模型编写了Laravel模型工厂,可以直接通过工厂级联生成带关联的模型实例,不需要手动调用setRelation(),使用make()方法生成实例不会向数据库写入测试数据:
$app->bind(Service::class, function () { return $this->mock(Service::class, function(MockInterface $mock) { $mock->makePartial(); $check = Check::factory() ->has( User::factory()->has( Verification::factory()->state(['some_id' => 123]), 'verification' ), 'user' ) ->make(); $mock->shouldReceive('getCheck')->andReturn($check); }); });
使用makePartial()生成的Mock实例只会替换你通过shouldReceive指定的方法,其余类方法会执行原有逻辑,当前场景下仅Mock trait提供的getCheck方法,其余业务逻辑走原生实现,符合单元测试的隔离要求。
内容的提问来源于stack exchange,提问作者mrzalais

