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(...);
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:
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; }
.Person
class overrides equals
: the argument must have value equality to inPerson
(i.e. Objects.equals(argument, inPerson)
).