如何使用PHPUnit Mock Laravel Eloquent模型并设置自定义属性
问题原因
你使用PHPUnit默认创建的是完整Mock对象,所有原生方法都会被替换为空桩(默认返回null):
- Eloquent模型的属性赋值(
$user->id = $id)本质是调用魔术方法__set(),该方法已经被Mock覆盖,不会执行原生的属性写入逻辑 - 同理
setAttribute()方法也被Mock为空,赋值后不会存入模型的属性数组,自然读取时返回null
可行解决方案
方案1:为属性访问方法打桩(最简便,适合仅需设置少量属性的场景)
Eloquent读取属性最终都会走getAttribute()方法,直接为该方法打桩指定返回值即可:
/** * @param int $id * @return User|\PHPUnit\Framework\MockObject\MockObject */ protected function createMockUser(int $id): \PHPUnit\Framework\MockObject\MockObject|User { $user = $this ->getMockBuilder(User::class) ->disableOriginalConstructor() ->getMock(); // 为id属性的访问打桩 $user->method('getAttribute') ->with('id') ->willReturn($id); return $user; }
如果需要支持多属性返回,可以调整桩逻辑:
$attributes = ['id' => $id, 'name' => 'test']; $user->method('getAttribute') ->willReturnCallback(fn($key) => $attributes[$key] ?? null);
方案2:使用部分Mock,保留原生属性操作逻辑
如果你需要保留模型原生的属性读写逻辑,仅Mock你需要自定义的业务方法(如save、delete等),可以通过onlyMethods指定要替换的方法:
protected function createMockUser(int $id): \PHPUnit\Framework\MockObject\MockObject|User { $user = $this ->getMockBuilder(User::class) // 仅替换你需要自定义逻辑的方法,属性相关方法保留原生实现 ->onlyMethods(['save', 'delete']) ->disableOriginalConstructor() ->getMock(); // 此时可以正常使用原生方法赋值 $user->id = $id; return $user; }
方案3:直接实例化模型(无特殊隔离需求时首选)
如果你的测试不需要完全隔离模型的基础逻辑,完全可以跳过PHPUnit Mock,直接实例化模型设置属性,代码更简洁:
protected function createMockUser(int $id): User { return User::make(['id' => $id]); }
内容的提问来源于stack exchange,提问作者k0pernikus
相关产品推荐
相关产品推荐

