I'm encountering this Wanted but not invoked error while writing tests for the PostService class using Mockito. The error message suggests that some error occurred while invoking the save method from the post repository. Here's the error message:
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)
The error seems to be happening due to a mismatch between the stubbing of the modelMapper.map method and its invocation in the PostService class during the update method. I've looked into the error message, and I'm unsure how to resolve this issue. Here's the relevant code:
Service class:
@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 class:
@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);
}
}
I'm unable to determine what's causing this problem and how to resolve it. Any help in understanding the issue and finding a solution would be greatly appreciated.
You have set up your repository to return post
, which is then saved by your service method:
// stub:
doReturn(Optional.of(post)).when(postRepository).findById(post.getId());
// service:
Optional<Post> postOptional = postRepository.findById(uuid);
// ...
return Optional.of(modelMapper.map(
postRepository.save(post), // <-- saved here
PostResponseDto.class));
Yet, you verify that save
has been called with mockUpdatePost
, which is a different instance:
verify(postRepository, times(1)).save(mockUpdatedPost);
This can also be seen from the error message:
Wanted but not invoked:
postRepository.save(
com.example.justatest.post.Post@6edaa77a // <-- instance 6edaa77a
);
-> at com.example.justatest.post.PostServiceTest.shouldUpdatePost(PostServiceTest.java:139)
However, there were exactly 2 interactions with this mock:
...
postRepository.save(
com.example.justatest.post.Post@5bfc257 // <-- instance 5bfc257
);
-> at com.example.justatest.post.PostService.update(PostService.java:45)
Fix your verify call to expect the correct instance:
verify(postRepository).save(post);
PS. When stubbing the calls on your mock repository, you are using the correct post
instance:
doReturn(mockUpdatedPost).when(postRepository).save(post);
With strict stubbing mode of Mockito (the default in newer versions), the test will fail if a stubbed method has not been called, so the verify
call is actually redundant (but you might still prefer it for explicitly clarifying your intent).