javatestingjunitmockitostubbing

Stubbing properly with postForEntity (returning ResponseEntity<DTO>) using Mockito


I am currently facing an odd behavior during my test,

The goal was to test the following registration method :

@RestController
@RequiredArgsConstructor
public class RegisterController {

    ...

    private final String secret = "";
    private final String url = "https://www.google.com/recaptcha/api/siteverify";
    
    private final UserService userService;
    private final VerificationTokenService tokenService;
    private final MailingService mailingService;

    ...

    @PostMapping("/api/auth/register")
    public ResponseEntity<RestUser> register(@Valid @RequestBody User user, @RequestParam("token") String token) {
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        
        map.add("secret", secret);
        map.add("response", token);
        
        // Preparing the request and extracts the response content.
        RestTemplate restTemplate = new RestTemplate();
        
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
        ResponseEntity<CaptchaResponse> response = restTemplate.postForEntity(url, request, CaptchaResponse.class);
        CaptchaResponse captchaResponse = response.getBody();
        
        // Response is 200 OK (during test).
        System.out.println(response);
        
        // The body contains CaptchaResponse (during test).
        System.out.println(response.getBody());
        
        // Always false (during test) even with mocking.
        System.out.println(captchaResponse.isSuccess());

        // If the captcha failed (always fails during tests).
        if (captchaResponse == null || !captchaResponse.isSuccess()) return ResponseEntity.badRequest().build();
        
        ...
        
        // Returns the programmed rest user (without the disclosure of the token) as a positive response.
        return ResponseEntity.ok(u);
    }
    
    ...
}

And so far the test written was :

@SpringBootTest
public class RegisterControllerTest {
    
    private MockMvc mock;
    
    @Autowired
    private RegisterController registerController;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @MockBean
    private UserService userService;
    
    @MockBean
    private VerificationTokenService tokenService;
    
    @MockBean
    private MailingService mailingService;
    
    @Mock
    private RestTemplate rest;
    
    // We don't want to repeat ourselves.
    private static User registeringUser;
    
    @BeforeAll
    public static void initialize() {
        List<Role> defaultRoles = new ArrayList<Role>();
        defaultRoles.add(new Role("USER"));
        
        // Classic user that will attempt to register.
        registeringUser = new User("test@example.com", "newenpoi", ".azerty");
        registeringUser.setRoles(defaultRoles);
    }
    
    @BeforeEach
    public void setup() {
        MockitoAnnotations.openMocks(this);
        mock = MockMvcBuilders.standaloneSetup(registerController).build();
    }

    @Test
    @DisplayName("Test registration of a lambda user for production condition with valid captcha.")
    public void testRegisterProduction_shouldReturnRestUser_whenCaptchaSuccess() throws Exception {
        // Given.
        String token = "valid-token";
        
        RestUser expectedRest = new RestUser(registeringUser);
        VerificationToken expectedToken = new VerificationToken();
        
        // Simulate captcha response.
        CaptchaResponse captchaResponse = new CaptchaResponse();
        captchaResponse.setSuccess(true);
        
        ResponseEntity<CaptchaResponse> responseEntity = new ResponseEntity<>(captchaResponse, HttpStatus.OK);
        
        // When.
        when(rest.postForEntity(anyString(), any(HttpEntity.class), eq(CaptchaResponse.class))).thenReturn(responseEntity);
        
        // Then.
        mock.perform(post("/api/auth/register").param("token", token).contentType("application/json").content(objectMapper.writeValueAsString(registeringUser))).andExpect(status().isOk());
    }
}

Whatever I attempt to do the success field from captchaResponse will always remain false, and I can't find what I am missing in my test in order to pass the if (captchaResponse == null || !captchaResponse.isSuccess()) condition!

This is the CaptchaResponse object for reference :

@RequiredArgsConstructor
@Getter
@Setter
@ToString
public class CaptchaResponse {
    private boolean success;
    private String challengeTs;
    private String hostname;
    private String[] errorcodes;
}

I did my best to follow good practices, also the debug does print a response that contains a CaptchaResponse but isSuccess() will always be false during tests and the other fiels remains null even when I explicitely set values during the test (I only kept isSuccess in the above for testing).

Ive been browsing on Stack here and there but I couldn't find unfortunately a concrete solution to this behavior.

Any help pointing me to the right direction is greatly appreciated!


Solution

  • You create a new instance of RestTemplate in RegisterController, and in the test you create a mock of RestTemplate bean. Try injecting a bean managed by Spring.

    @RestController
    @RequiredArgsConstructor
    public class RegisterController {
    
    private final RestTemplate restTemplate;