简体   繁体   中英

Mockito is returning "java.lang.IllegalArgumentException: URI is not absolute" in RestTemplate.exchange Springboot

Mockito is returning "java.lang.IllegalArgumentException: URI is not absolute" in RestTemplate.exchange. I am not sure why this is happening because it seems I am mocking the restTemplate properly and since I am seeing that exception, it seems that RestTemplate is not a mock.

Here is my class

@Component
public class RestTemplateWrapper {
  private static final Logger LOGGER = LoggerFactory.getLogger(RestTemplateWrapper.class);
  
  public <T> ResponseEntity<T> callWebServiceGET(String url,HttpEntity<?> httpEntity,
      ParameterizedTypeReference<T> parameterizedTypeReference) {

    RestTemplate restTemplate = new RestTemplate();
    ResponseEntity<T> response=null;
    LOGGER.trace("Entered callWebServiceGET");
    LOGGER.info("Calling WebService {}", url);
    try {
      response=restTemplate.exchange(url, HttpMethod.GET, httpEntity, parameterizedTypeReference);
    } catch (HttpClientErrorException e) {
      if (HttpStatus.NOT_FOUND.equals(e.getStatusCode())) {
        LOGGER.error("Service Unavailable - Code 404 returned. " + url + e.getMessage());
      } else if (HttpStatus.UNAUTHORIZED.equals(e.getStatusCode())) {
        LOGGER.error("Token Expired- Code 401 returned. " + e.getMessage());
      } else if (HttpStatus.BAD_REQUEST.equals(e.getStatusCode())) {
        LOGGER.error("Bad Input, 400 returned.{} {} ", url , e.getMessage(), e);
      } else {
        LOGGER.error("WEB Service Failure. " + e.getMessage());
      }
    }
    return response;
  }

}

And here is my TestCase:

@PrepareForTest({RestTemplateWrapper.class})
public class RestTemplateWrapperTest  {
  
  @Mock
  private RestTemplate mockRestTemplate;
  @InjectMocks
  private RestTemplateWrapper webUtils;
  @Before
  public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
  }
  @Test
  public void callWebServiceGET_OK()  {
    HttpEntity<String> httpEntity= new ResponseEntity<>(HttpStatus.OK);
    ResponseEntity<String> entityResponse=new ResponseEntity<>("MOCK_RESPONSE", HttpStatus.OK);
    when(mockRestTemplate.exchange(eq("/objects/get-objectA"), eq(HttpMethod.GET), eq(httpEntity),any(
        ParameterizedTypeReference.class))).thenReturn(
        entityResponse);
    ResponseEntity<String> mockResponse= webUtils.callWebServiceGET("",null,  new ParameterizedTypeReference<String>(){
    });
    //verify(mockRestTemplate,times(1)).exchange(Matchers.anyString(), Matchers.any(), Matchers.any());
     Assert.assertEquals("MOCK_RESPONSE",mockResponse.getBody());
  }
  
}

The response:

URI is not absolute
java.lang.IllegalArgumentException: URI is not absolute
    at java.net.URI.toURL(URI.java:1088)
    at org.springframework.http.client.SimpleClientHttpRequestFactory.createRequest(SimpleClientHttpRequestFactory.java:145)
    at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:87)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:727)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:666)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:604)
    at com.project.di.tp.purchaseorderservice.utils.RestTemplateWrapper.callWebServiceGET(RestTemplateWrapper.java:29)
    at com.project.di.tp.purchaseorderservice.utils.RestTemplateWrapperTest.callWebServiceGET_OK(RestTemplateWrapperTest.java:51)

Any idea about how to solve this issue? I have been trying like 4 hours.

Although it is not stated explicitly in the JavaDocs it is the case that you have to provide an absolute URL there.

This is because you do nowhere provide a base URL where a relative URL would be relative to. You could not enter "/objects/get-objectA" as URL in your browser either.

So I would suggest that you use something like "http://example.com/objects/get-objectA" instead for the first parameter:

when(mockRestTemplate.exchange(
        eq("http://example.com/bla"), 
        eq(HttpMethod.GET), 
        isNull(HttpEntity.class), 
        any(ParameterizedTypeReference.class))).
    thenReturn(entityResponse);

ResponseEntity<String> mockResponse =
    webUtils.callWebServiceGET(
        "http://example.com/bla", 
        null, 
        new ParameterizedTypeReference<String>(){});

Please note that the call to webUtils.callWebServiceGET with given parameters would not make Mockito return the wanted answer, so I changed for one the URL in the call to the absolute URL you are expecting in the Mockito.when and also changed the parameter expected there to be a typed null (typed to match the method signature).

UPDATE:

As you found out by yourself already, your Mockito.when doesn't work because you do not use the created mock from the test in your tested method, but instead create a fresh instance of RestTemplate in each call of callWebServiceGET . (Don't know why I didn't see it earlier, sorry!)

I recommend that instead you inject the RestTemplate into the tested class with a constructor:

private final RestTemplate restTemplate;
public RestTemplateWrapper(RestTemplate restTemplate) {
  this.restTemplate = restTemplate;
}

// remove the following line in the method callWebServiceGET:
// RestTemplate restTemplate = new RestTemplate();

With this code, Spring will automatically inject your mocked RestTemplate into the test, but for running the production code you need to add a bean to provide a RestTemplate for injection.

Add this to a Configuration class where you also define other beans:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
   // Do any additional configuration here
   return builder.build();
}

(Found this code snippet in an answer to How to autowire RestTemplate using annotations )

And as a general advice for testing: try to avoid the use of the new operator in any code you want to test, but use injection instead. If you need to create multiple instances (eg in a loop, etc.) try to inject a factory that creates the instances for you - so in the test you can mock the factory.

I found the solution, it seems the problem is that my class is RestTemplateWrapper is creating a instance inside callWebServiceGET therefore mockito can`t mock that object. If if set the object outside the method, it works but I dont want to do that.

Is there any way to mock a object that is inside a method?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM