2

I have a requirement to unit test a method that makes an external API call using RestTemplate for which I am trying to mock the response of external API and assert the return value of the method. I have also tried to mock the response using MockRestServiceServer and @InjectMocks but was unable to get the expected output and it makes the actual API call. Any guidance will be appreciated.


Test Case

public class SnowFlakeServiceTest {

  private final RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
  private final SnowFlakeServiceImpl snowFlakeService = new SnowFlakeServiceImpl(restTemplate);

  @Test
  public void testGetAccessToken() {

    SnowFlakeTokenDTO snowFlakeTokenDTO = new SnowFlakeTokenDTO();
    snowFlakeTokenDTO.setAccessToken("fakeAccessToken");
    snowFlakeTokenDTO.setExpiresIn(600);
    snowFlakeTokenDTO.setTokenType("Bearer");

    ResponseEntity<SnowFlakeTokenDTO> responseEntity = new ResponseEntity<>(snowFlakeTokenDTO, HttpStatus.OK);
    when(restTemplate.exchange(
        ArgumentMatchers.anyString(),
        ArgumentMatchers.any(HttpMethod.class),
        ArgumentMatchers.any(),
        ArgumentMatchers.<Class<SnowFlakeTokenDTO>>any()))
        .thenReturn(responseEntity);

    assertThat(snowFlakeService.getAccessToken()).isEqualTo(snowFlakeTokenDTO.getAccessToken());
  }

}

SnowFlakeServiceImpl.java

 private final RestTemplate restTemplate;

  public SnowFlakeServiceImpl(RestTemplate restTemplate) {
    this.restTemplate = restTemplate;
  }

@Override
  public String getAccessToken() {
    log.debug("token requested id : {}, secret :{}", snowFlakeClientId, snowFlakeClientSecret);
    try {
      HttpHeaders httpHeaders = new HttpHeaders();
      httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
      httpHeaders.setBasicAuth(snowFlakeClientId, snowFlakeClientSecret);

      MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
      requestBody.add("grant_type", snowFlakeRefreshTokenGrantType);
      requestBody.add("refresh_token", refreshToken);
      requestBody.add("redirect_uri", snowFlakeRedirectUrl);

      HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestBody, httpHeaders);
      ResponseEntity<SnowFlakeTokenDTO> response = restTemplate.exchange(snowFlakeTokenRequestUrl, HttpMethod.POST, request, SnowFlakeTokenDTO.class);
      log.debug("response: {}", response);
      return Objects.requireNonNull(response.getBody()).getAccessToken();
    } catch (Exception e) {
      log.debug("error: {}", ExceptionUtils.getRootCauseMessage(e));
      throw new ErrorDTO(Status.BAD_REQUEST, accessTokenErrorMessage, e.getMessage());
    }
  }

RestTemplateConigurations.java

@Configuration
public class RestTemplateConfiguration {

  @Value("${rest-template.connection.timeout}")
  private int connectionTimeout;

  @Value("${rest-template.read.timeout}")
  private int readTimeout;

  @Bean
  public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder
        .setConnectTimeout(Duration.ofMillis(connectionTimeout))
        .setReadTimeout(Duration.ofMillis(readTimeout))
        .build();
  }
}

Spring Boot : 2.5.4
Java : 11
Junit : 5

anonymous
  • 173
  • 1
  • 15
  • 3
    `getAccessToken()` is creating a new instance of `RestTemplate`, so the instance `getAccessToken()` is using is unrelated to the mock instance in the test. `RestTemplate` should be a dependency injected into the class that contains `getAccessToken()` (instead of the method creating a new instance). Then, in the test, the mock can be injected. – Andrew S Nov 19 '21 at 13:25
  • Check my yesterdays answer at: https://stackoverflow.com/questions/70010268/spring-mockito-test-of-resttemplate-postforentity-throws-illegalargumentexceptio/ Same error - RestTemplate not injected to SUT. – Lesiak Nov 19 '21 at 13:25
  • Thanks, @AndrewS, Lesiak, I have changed the code and updated the question. Can you please have a look? – anonymous Nov 19 '21 at 16:47
  • Thanks, @Lesiak, I have changed the code and updated the question. Can you please have a look? – anonymous Nov 22 '21 at 04:26
  • Answer by Joao looks good to me. – Lesiak Nov 22 '21 at 08:17

1 Answers1

3

You don't need a new RestTemplate every time you call the getAccessToken() method. It should instead be injected by Spring:

@Service
public class SnowFlakeService {

  private RestTemplate restTemplate;

  public ServiceImpl(RestTemplate restTemplate) {
      this.restTemplate = restTemplate;
  }

  @Override
  public String getAccessToken() {
    try {
      HttpHeaders httpHeaders = new HttpHeaders();
      httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
      httpHeaders.setBasicAuth(snowFlakeClientId, snowFlakeClientSecret);

      MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
      requestBody.add("grant_type", snowFlakeRefreshTokenGrantType);
      requestBody.add("refresh_token", refreshToken);
      requestBody.add("redirect_uri", snowFlakeRedirectUrl);

      HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestBody, httpHeaders);
      ResponseEntity<SnowFlakeTokenDTO> response = restTemplate.exchange(snowFlakeTokenRequestUrl, HttpMethod.POST, request, SnowFlakeTokenDTO.class);
      
      return Objects.requireNonNull(response.getBody()).getAccessToken();
    } catch (Exception e) {
      throw new ErrorDTO(Status.BAD_REQUEST, accessTokenErrorMessage, e.getMessage());
    }
  }
}

On top of this, you don't actually need a @SpringBootTest in order to test SnowFlakeService. A regular unit test is pretty much ok.

public class SnowFlakeServiceTest {

  private RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
  private SnowFlakeService snowFlakeService = new SnowFlakeService(restTemplate);

  @Test
  public void testGetAccessToken() {
    SnowFlakeTokenDTO snowFlakeTokenDTO = new SnowFlakeTokenDTO();
    snowFlakeTokenDTO.setAccessToken("fakeAccessToken");
    snowFlakeTokenDTO.setExpiresIn(600);
    snowFlakeTokenDTO.setTokenType("Bearer");

    ResponseEntity<SnowFlakeTokenDTO> responseEntity = new ResponseEntity<>(snowFlakeTokenDTO, HttpStatus.OK);
    when(restTemplate.exchange(
        ArgumentMatchers.anyString(),
        ArgumentMatchers.any(HttpMethod.class),
        ArgumentMatchers.any(),
        ArgumentMatchers.<Class<SnowFlakeTokenDTO>>any()))
        .thenReturn(responseEntity);

    assertThat(snowFlakeService.getAccessToken()).isEqualTo(snowFlakeTokenDTO.getAccessToken());
  }

}
João Dias
  • 16,277
  • 6
  • 33
  • 45
  • Thanks, @Joao. I have tried to change the code as per your suggestion but from the test, it is still making an actual call to external API and not mocking the response. I have updated the code in question can you please have a look at it? – anonymous Nov 19 '21 at 16:37
  • That can't be it. `restTemplate` is mocked in the test that I've added in my answer. Please recheck it. – João Dias Nov 19 '21 at 18:35
  • Thanks, @Joao, It works. Do you know how I can mock the response of external POST APIs called within the same method as when I have tried to do so it was giving an unable cast class exception – anonymous Nov 22 '21 at 10:34
  • Sorry, I am not following you. Isn't this already mocking the response of a POST call to an external API? I would like to answer your question but I am a bit lost. – João Dias Nov 22 '21 at 10:38