如何在Laravel中模拟Eloquent的HasOne关联进行单元测试?
解决Eloquent HasOne关联模拟时的create方法报错问题
我之前也踩过Mockery模拟Eloquent关联的坑,你遇到的这个报错核心原因很明确:Mockery要求所有对模拟对象的方法调用都必须提前设置期望,也就是告诉它“这个方法会被调用,参数是什么,应该返回什么”,否则就会抛出Received Mockery_xxx::create(), but no expectations were specified的错误。
针对你这个场景,给你几个具体的解决方案:
1. 完整模拟关联链路并设置create期望
你需要一步步模拟从主模型到HasOne关联,再到create方法的整个调用链:
// 1. 先模拟关联模型RelationTable的实例 $relationTableMock = Mockery::mock(RelationTable::class); // 2. 模拟HasOne关联对象,设置create方法的调用期望 $hasOneMock = Mockery::mock(HasOne::class); $hasOneMock->shouldReceive('create') // 验证传入的参数是否符合预期(如果不需要严格校验,可用withAnyArgs()) ->with([ 'field_1' => 'value_1', 'field_2' => 'value_2', 'field_3' => 'value_3', ]) // 指定create方法调用后返回的对象 ->andReturn($relationTableMock); // 3. 让你的主模型(替换成实际类名)的relationTable方法返回这个模拟的HasOne对象 $save = Mockery::mock(YourMainModel::class); $save->shouldReceive('relationTable') ->andReturn($hasOneMock);
记得把YourMainModel替换成你实际使用的主模型类名。
2. 用部分模拟保留主模型原有功能
如果你的主模型除了关联方法外,其他方法还需要正常执行,可以用部分模拟(Partial Mock):
// 创建主模型的部分模拟实例,保留原有未被模拟的方法逻辑 $save = Mockery::mock(YourMainModel::class)->makePartial(); $relationTableMock = Mockery::mock(RelationTable::class); $hasOneMock = Mockery::mock(HasOne::class); $hasOneMock->shouldReceive('create') ->with([ 'field_1' => 'value_1', 'field_2' => 'value_2', 'field_3' => 'value_3', ]) ->andReturn($relationTableMock); $save->shouldReceive('relationTable') ->andReturn($hasOneMock);
3. 简化:仅验证create方法被调用(无需返回值)
如果你的测试只需要确保create方法被正确调用,不需要关心返回结果,可以简化成这样:
$hasOneMock->shouldReceive('create') ->with([ 'field_1' => 'value_1', 'field_2' => 'value_2', 'field_3' => 'value_3', ]) ->once(); // 确保方法只被调用一次,可用->times(n)指定调用次数
总结一下,核心就是要给模拟的HasOne对象的create方法明确设置调用期望,告诉Mockery如何处理这个方法调用,它就不会再抛出那个错误了。
内容的提问来源于stack exchange,提问作者Rodman Tode
相关产品推荐
相关产品推荐

