使用Mockito测试Spring Boot PostService时遇Wanted but not invoked错误
Mockito测试PostService时出现"Wanted but not invoked"错误
错误信息
Wanted but not invoked: postRepository.save( com.example.justatest.post.Post@6edaa77a ); -> at com.example.justatest.post.PostServiceTest.shouldUpdatePost(PostServiceTest.java:139) However, there were exactly 2 interactions with this mock: postRepository.findById( c74dd65f-2438-4137-9d6d-a664a3f95621 ); -> at com.example.justatest.post.PostService.update(PostService.java:36) postRepository.save( com.example.justatest.post.Post@5bfc257 ); -> at com.example.justatest.post.PostService.update(PostService.java:45) Wanted but not invoked: postRepository.save( com.example.justatest.post.Post@6edaa77a ); -> at com.example.justatest.post.PostServiceTest.shouldUpdatePost(PostServiceTest.java:139) However, there were exactly 2 interactions with this mock: postRepository.findById( c74dd65f-2438-4137-9d6d-a664a3f95621 ); -> at com.example.justatest.post.PostService.update(PostService.java:36) postRepository.save( com.example.justatest.post.Post@5bfc257 ); -> at com.example.justatest.post.PostService.update(PostService.java:45)
相关代码
Service类
@Service @RequiredArgsConstructor public class PostService { private final PostRepository postRepository; private final ModelMapper modelMapper; public Optional<PostResponseDto> update(UUID uuid, PostRequestDto postRequestDto) { Optional<Post> postOptional = postRepository.findById(uuid); if (postOptional.isEmpty()) { return Optional.empty(); } Post post = postOptional.get(); modelMapper.map(postRequestDto, post); return Optional.of(modelMapper.map(postRepository.save(post), PostResponseDto.class)); } // ... (other methods) }
Test类
@ExtendWith(MockitoExtension.class) class PostServiceTest { @Mock private PostRepository postRepository; @Mock private ModelMapper modelMapper; @InjectMocks private PostService postService; private Post post; @BeforeEach void setUp() { this.post = new Post(UUID.randomUUID(), "title", "description"); } @Test void shouldUpdatePost() { PostRequestDto mockPostRequestDto = new PostRequestDto("Updated Title", "Updated Description"); Post mockUpdatedPost = new Post(post.getId(), mockPostRequestDto.getTitle(), mockPostRequestDto.getDescription()); PostResponseDto mockPostResponseDto = new PostResponseDto(mockUpdatedPost.getId(), mockUpdatedPost.getTitle(), mockUpdatedPost.getDescription()); doReturn(Optional.of(post)).when(postRepository).findById(post.getId()); doNothing().when(modelMapper).map(mockPostRequestDto, post); doReturn(mockUpdatedPost).when(postRepository).save(post); doReturn(mockPostResponseDto).when(modelMapper).map(mockUpdatedPost, PostResponseDto.class); Optional<PostResponseDto> updatedPostOptional = postService.update(post.getId(), mockPostRequestDto); assertTrue(updatedPostOptional.isPresent()); PostResponseDto updatedPost = updatedPostOptional.get(); assertEquals(mockPostResponseDto.getId(), updatedPost.getId()); assertEquals(mockPostResponseDto.getTitle(), updatedPost.getTitle()); assertEquals(mockPostResponseDto.getDescription(), updatedPost.getDescription()); verify(postRepository, times(1)).findById(post.getId()); verify(postRepository, times(1)).save(mockUpdatedPost); verify(modelMapper, times(1)).map(mockPostRequestDto, post); verify(modelMapper, times(1)).map(mockUpdatedPost, PostResponseDto.class); } }
问题原因及解决方案
核心原因
测试中的verify(postRepository, times(1)).save(mockUpdatedPost);语句存在对象不匹配问题:
- Service的update方法里,调用
postRepository.save(post)时,传入的是从findById获取到的原Post实例(即使经过modelMapper修改,仍是同一个对象引用) - 但测试中验证的是手动创建的
mockUpdatedPost实例,这是另一个独立的Post对象,Mockito默认通过对象引用(或equals方法)匹配调用,因此判定为未调用期望的方法,抛出错误。
解决方案
方案1:修改verify语句匹配实际传入的对象
直接将verify语句中的mockUpdatedPost替换为原post对象:
verify(postRepository, times(1)).save(post);
方案2:使用ArgumentCaptor捕获参数并验证属性
这种方式更灵活,能验证save的对象属性是否符合预期,同时避免对象引用匹配的问题:
- 在测试类中添加ArgumentCaptor:
@Captor private ArgumentCaptor<Post> postCaptor;
- 修改verify逻辑,捕获save的参数并验证属性:
// 替换原有的save verify语句 verify(postRepository, times(1)).save(postCaptor.capture()); Post savedPost = postCaptor.getValue(); // 验证属性是否正确更新 assertEquals(mockUpdatedPost.getTitle(), savedPost.getTitle()); assertEquals(mockUpdatedPost.getDescription(), savedPost.getDescription());
额外优化:模拟ModelMapper的真实行为
当前测试中doNothing().when(modelMapper).map(mockPostRequestDto, post);只是空实现,原post对象的属性并未被修改,和真实业务逻辑不符。可以修改桩代码,模拟ModelMapper的属性复制行为:
// 替换原有的modelMapper map桩代码 doAnswer(invocation -> { Post targetPost = invocation.getArgument(1); targetPost.setTitle(mockPostRequestDto.getTitle()); targetPost.setDescription(mockPostRequestDto.getDescription()); return null; }).when(modelMapper).map(mockPostRequestDto, post);
内容的提问来源于stack exchange,提问作者Eduardo
相关产品推荐
相关产品推荐

