Search code examples
javamockitoresttemplate

Why is my unit test failing with "URI is not absolute" when I try to mock RestTemplate.postForEntity()?


I want to mock the postForEntity method of a RestTemplate instance variable belonging to one of my classes, but it keeps failing with a "URI is not absolute" error despite my seeing anyString() being passed in as the URI in other examples. Something that should be noted is that I did not @Mock my RestTemplate, because I want to test if it's been correctly built by the constructor of its parent class (namely the timeout).

I can't post actual code but the class I'm testing looks something like this:

public class MyClient {
    ...
    @Getter
    @Setter
    private RestTemplate restTemplate = new RestTemplate();

    public MyClient(Property x, Property y, Property z){
        this.x = x; 
        this.y = y;
        this.z = z; 
    }

    //New constructor that I want to test 
    public MyClient(Property x, Property y, Property z, int timeout){
        this(x, y, z);
        this.restTemplate =((SimpleClientHttpRequestFactory)getRestTemplate().getRequestFactory()).setReadTimeout(timeout);
    }
    
    public makeRequests() {
        ...
        //myEndpoint, myRequest are assigned earlier in this fx, CustomResponse is a custom class
        ResponseEntity resEntity = getRestTemplate().postForEntity(myEndpoint, myRequest, CustomResponse.class);
    }
}

I want to test whether MyClient.restTemplate is timing out:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest extends TestCase{
    ...
    
    @Test
    public void test_new_constructor(){
        MyClient myClient = new MyClient(x, y, z, 1000);
        RestTemplate restTemplate = myClient.getRestTemplate(); 
        Mockito.when(restTemplate.postForEntity(anyString(), any(), any(Class.class))).thenAnswer(new AnswersWithDelay(100000, new Returns(new ResponseEntity(CustomResponse, HttpStatus.OK)) ));
        //Will eventually add a delay to trigger a timeout + code for checking the exception, but it doesn't make it past the above line
        myClient.makeRequests();
    }
}

It fails on the Mockito.when(restTemplate.postForEntity(anyString(), any(), any(Class.class))).thenAnswer(new AnswersWithDelay(100000, new Returns(new ResponseEntity(CustomResponse, HttpStatus.OK)) )); line with java.lang.IllegalArgumentException: URI is not absolute, why is this? Is it a bad idea to test an actual RestTemplate in the first place? I was thinking that using Mockito.when on the network calls would make it so that the tests aren't reliant on requests to actual endpoints, but is this not the case?


Solution

  • If you haven't mocked an instance, you cannot call when with it. when is reserved for mock objects or spies.

    The line Mockito.when(restTemplate.postForEntity(anyString(), any(), any(Class.class))) calls the real postForEntity method with parameters anyString(), any(), any(Class.class), which is really just "", null, null:

    ArgumentMatchers#anyString:

    public static String anyString() {
        reportMatcher(new InstanceOf(String.class, "<any string>"));
        return "";
    }
    

    #any:

    public static <T> T any() {
        reportMatcher(Any.ANY);
        return null;
    }
    

    The empty string "" obviously isn't an absolute URI.

    If you want to test a class, you must not mock it; otherwise you would only be testing the mock object/mock behavior and not your real class.

    That said, I think it is fair to assume that RestTemplate has already been thoroughly tested by the framework authors – why do you want to test if it detects/triggers timeouts correctly? That should already be covered by the tests for RestTemplate.

    If, however, you want to test if your application code handles timeouts of RestTemplate correctly, then by all means create a mock instance of RestTemplate, set it up to behave like it timeouted, and verify that your own code handles the timeout of the template correctly.