Search code examples
javaspring-bootmockitointegration-testingspring-mvc-test

Mocked service class returns false instead of defined value


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);
      }