Search code examples
spring-bootmockito

Spring Boot - Mockito - Argument matchers for exact match - not working


I have a simple spring boot app with controller (example)

@RestController
@RequestMapping("/person")
public class PersonController {
    @Autowired
    PersonService personService;
    @PostMapping
    ResponseEntity<Void> createNewMessage(@RequestBody Person itemDto, UriComponentsBuilder uriComponentsBuilder){
        
        final Person itemInfo = personService.createPerson(itemDto)
                .orElseThrow(()-> new DuplicateException("ALREADY_EXISTS"));
        //NOTE: assume there will be a get end point
        UriComponents uriComponents = uriComponentsBuilder.path("/person/find/{id}").buildAndExpand(itemInfo.getId());

        return ResponseEntity.created(uriComponents.toUri()).build();
    }
}

Goal: To be able to "Unit" test DuplicateException in the endpoint using MockMvc - Mockito - "Arrange using when"

Source: https://github.com/kswat/demo.git

This test fails ( Note If "any" is used the test passes - any(Person.class) ) , but what is wrong?

@Test
void given_exact_person_save() throws Exception {
    var inPerson = new Person("First","Last");

    when(personService.createPerson(ArgumentMatchers.eq(inPerson))).thenReturn(Optional.of(inPerson));

    mockMvc.perform(post("/person")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(inPerson)))
            .andExpect(status().isCreated());
}

Service - dummy implementation

@Service
public class PersonService {

    public Optional<Person> createPerson(Person itemDto) {
        return Optional.empty();
    }
}

How to complete my test ?

may be have 2 Arrange statements

when(personService.createPerson(ArgumentMatchers.eq(inPerson))).thenReturn(...);
when(personService.createPerson( any(Person.class)) ).thenReturn(...);

Solution

  • Let's have a look at each piece of your code in detail:

    @PostMapping
    ResponseEntity<Void> createNewMessage(@RequestBody Person itemDto, UriComponentsBuilder uriComponentsBuilder) {
    

    deserializes itemDto from your request's (JSON) payload.

    mockMvc.perform(post("/person")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(inPerson)))
    

    very explicitly serializes your inPerson instance to a JSON string (objectMapper.writeValueAsString(inPerson)). This JSON string is completely detached from any existing Java object instance.

    var inPerson = new Person("First","Last");
    
    when(personService.createPerson(ArgumentMatchers.eq(inPerson)))
      .thenReturn(Optional.of(inPerson));
    

    matches a call to createPerson with an argument that is equal to inPerson. That means either:

    • If your Person class does not override the equals method: the argument must be the same reference as inPerson (i.e. argument == inPerson, both instances have the same identity hashcode). Note that it is still calling equals behind the scenes, but the default implementation of Object#equals is boolean equals(Object other) { return this == other; }.
    • Your Person class overrides equals: the argument must have value equality to inPerson (i.e. Objects.equals(argument, inPerson)).