Issue
I'm currently trying to write Integration tests for a registration controller. When I run the test I get a different return value from the one I define using Mockito's when()
method.
Test method:
@Test
public void whenValidInput_thenReturnsTrue() throws Exception{
// given
UserRegistrationRequest req = new UserRegistrationRequest("testUsername", "testPassword");
when(registrationService.registerUser(req)).thenReturn(true);
// when
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(URL)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk())
.andReturn();
// then
assertThat(mvcResult.getResponse().getContentAsString())
.isEqualToIgnoringWhitespace("true");
}
Test output:
org.junit.ComparisonFailure:
Expecting actual:
"false"
to be equal to:
"true"
when ignoring whitespace differences
Expected :"true"
Actual :"false"
I'm not sure why this is happening - although I feel like it is most likely due to a incorrect configuration. I set the mocks using openMocks
in my setup method - but it still won't return the defined value. Every method except for whenValidInput_thenReturnsTrue
will pass
, although I don't actually define mock return values for those methods and instead verify arguments using captors; this probably isn't something that's method-specific.
RegistrationControllerTest
@RunWith(SpringJUnit4ClassRunner.class)
@WebMvcTest(value = RegistrationController.class)
public class RegistrationControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private RegistrationService registrationService;
@MockBean
private UserService userService;
@MockBean
private PasswordEncoder passwordEncoder;
public static final String URL = "/api/v1/register";
private AutoCloseable autoCloseable;
@Before
public void setUp() {
autoCloseable = MockitoAnnotations.openMocks(this);
}
@After
public void tearDown() throws Exception {
autoCloseable.close();
}
@Test
public void whenValidRegister_thenReturns200() throws Exception {
// given
UserRegistrationRequest req = new UserRegistrationRequest("testUsername", "testPassword");
mockMvc.perform(MockMvcRequestBuilders.post(URL)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk());
}
@Test
public void whenNullValueRegister_thenReturns400() throws Exception {
// given
UserRegistrationRequest req = new UserRegistrationRequest(null, "testPassword");
// when then
mockMvc.perform(MockMvcRequestBuilders.post(URL)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isBadRequest());
}
@Test
public void whenValidInput_thenMapsRegisterService() throws Exception {
// given
UserRegistrationRequest req = new UserRegistrationRequest("testUsername", "testPassword");
// when
mockMvc.perform(MockMvcRequestBuilders.post(URL)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk());
// then
ArgumentCaptor<UserRegistrationRequest> userArgumentCaptor = ArgumentCaptor.forClass(UserRegistrationRequest.class);
verify(registrationService).registerUser(userArgumentCaptor.capture());
assertThat(userArgumentCaptor.getValue().getUsername()).isEqualTo(req.getUsername());
assertThat(userArgumentCaptor.getValue().getPassword()).isEqualTo(req.getPassword());
}
@Test
public void whenValidInput_thenReturnsTrue() throws Exception{
// given
UserRegistrationRequest req = new UserRegistrationRequest("testUsername", "testPassword");
when(registrationService.registerUser(req)).thenReturn(true);
// when
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(URL)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk())
.andReturn();
// then
assertThat(mvcResult.getResponse().getContentAsString())
.isEqualToIgnoringWhitespace("true");
}
}
RegistrationController
@CrossOrigin("http://localhost:4200") // Replace with proxy
@RequestMapping("/api/v1/")
@RestController
public class RegistrationController {
private final RegistrationService registrationService;
public RegistrationController(RegistrationService registrationService) {
this.registrationService = registrationService;
}
@PostMapping("/register")
@ResponseStatus(HttpStatus.OK)
public boolean registerUser(@Valid @RequestBody UserRegistrationRequest request) {
return registrationService.registerUser(request);
}
}
UserRegistrationRequest
public class UserRegistrationRequest {
@NotNull private final String username;
@NotNull private final String password;
public UserRegistrationRequest(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
@Override
public String toString() {
return "UserRegistrationRequest{"
+ "username='"
+ username
+ '\''
+ ", password='"
+ password
+ '\''
+ '}';
}
}
Edit:
After changing the when
statement to this, the test will execute successfully.
when(registrationService.registerUser(Mockito.any(UserRegistrationRequest.class))).thenReturn(true);
After comparing the value passed to the registrationService
I got this output. The arguments appear to be the exact same.
Argument(s) are different! Wanted:
com.StruckCroissant.GameDB.registration.RegistrationService#0 bean.registerUser(
UserRegistrationRequest{username='testUsername', password='testPassword'}
);
-> at com.StruckCroissant.GameDB.registration.RegistrationControllerTest.whenValidInput_thenReturnsTrue(RegistrationControllerTest.java:130)
Actual invocations have different arguments:
com.StruckCroissant.GameDB.registration.RegistrationService#0 bean.registerUser(
UserRegistrationRequest{username='testUsername', password='testPassword'}
);
-> at com.StruckCroissant.GameDB.registration.RegistrationController.registerUser(RegistrationController.java:22)
Solution
As Lesiak pointed out, the UserRegistrationRequest
did not override the equals method. After adding the override for equals and hashCode to the class, the issue is resolved.
Additions to UserRegistrationRequest
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRegistrationRequest that = (UserRegistrationRequest) o;
return username.equals(that.username) && password.equals(that.password);
}
@Override
public int hashCode() {
return Objects.hash(username, password);
}
Answered By - StruckCroissant
Answer Checked By - David Marino (JavaFixing Volunteer)