Laravel模型Mocking测试问题:PHPUnit中update方法未被调用
解决PHPUnit中Mock Post模型update方法未被调用的问题
问题根源
你当前的测试代码存在核心逻辑错误:
- 你Mock的
$postMock对象仅定义了findOrFail的行为,但findOrFail返回的是真实的Post模型实例(由Post::factory()->make()生成),而非你Mock的对象。 - 后续调用
$post->update()时,执行的是真实模型的update方法,完全没用到你Mock的$postMock的预期,所以shouldReceive('update')永远不会被触发。 - 此外,
make()生成的模型是内存对象(未持久化到数据库),真实的update方法会返回false/null,且不会修改模型属性,导致日志中模型未更新。
解决方案
方案一:Mock实例+静态方法重载(基于Mockery)
让findOrFail返回Mock的Post实例,确保update调用触发Mock预期:
public function test_can_update_a_post(): void { $mockedTitle = 'Updated Title'; $mockedSlug = Str::slug($mockedTitle); $mockedPostData = ['title' => $mockedTitle, 'slug' => $mockedSlug]; // 准备更新后的模型数据 $updatedPostModel = Post::factory()->make($mockedPostData); // Mock单个Post实例,定义update行为 $postInstanceMock = Mockery::mock(Post::class); $postInstanceMock->shouldReceive('update') ->once() ->with($mockedPostData) ->andReturn($updatedPostModel); // 设置Mock实例的初始属性,模拟未更新状态 $postInstanceMock->title = 'Otho Pfeffer'; $postInstanceMock->slug = 'otho-pfeffer'; // 重载Post类,Mock静态方法findOrFail,返回上面的实例Mock $postClassMock = Mockery::mock('overload:' . Post::class); $postClassMock->shouldReceive('findOrFail') ->once() ->with(1) ->andReturn($postInstanceMock); $postService = new AdminPostService($postClassMock); $post = $postService->findOrFail(1); $result = $post->update($mockedPostData); // 断言更新结果符合预期 $this->assertEquals($updatedPostModel->title, $result->title); $this->assertEquals($updatedPostModel->slug, $result->slug); }
方案二:使用Laravel内置模型模拟(更简洁)
利用Laravel的Post::fake()功能,无需手动编写Mockery代码:
public function test_can_update_a_post(): void { $mockedTitle = 'Updated Title'; $mockedSlug = Str::slug($mockedTitle); $mockedPostData = ['title' => $mockedTitle, 'slug' => $mockedSlug]; // 模拟Post模型的数据库操作 Post::fake(); // 创建初始帖子并写入模拟数据库 $initialPost = Post::factory()->create([ 'title' => 'Otho Pfeffer', 'slug' => 'otho-pfeffer' ]); $postService = new AdminPostService(new Post()); $post = $postService->findOrFail($initialPost->id); $post->update($mockedPostData); // 断言模型属性已更新 $this->assertEquals($mockedTitle, $post->title); $this->assertEquals($mockedSlug, $post->slug); // 断言模拟数据库中的记录已更新 Post::assertExists([ 'id' => $initialPost->id, 'title' => $mockedTitle, 'slug' => $mockedSlug ]); }
内容的提问来源于stack exchange,提问作者Mr. Kenneth
相关产品推荐
相关产品推荐

