简体   繁体   中英

Spring Mockito test of RestTemplate.postForEntity throws IllegalArgumentException: URI is not absolute

My Controller calls the service to post information about a car like below and it works fine. However, my unit test fails with the IllegalArgumentException: URI is not absolute exception and none of the posts on SO were able to help with it.

Here is my controller

@RestController
@RequestMapping("/cars")  
public class CarController {

    @Autowired
    CarService carService;

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<CarResponse> getCar(@RequestBody CarRequest carRequest, @RequestHeader HttpHeaders httpHeaders) {

        ResponseEntity<CarResponse> carResponse = carService.getCard(carRequest, httpHeaders);

        return carResponse;
    }
}

Here is my service class:

@Service
public class MyServiceImpl implements MyService {

    @Value("${myUri}")
    private String uri;

    public void setUri(String uri) { this.uri = uri; }

    @Override
    public ResponseEntity<CarResponse> postCar(CarRequest carRequest, HttpHeaders httpHeaders) {
        List<String> authHeader = httpHeaders.get("authorization");

        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", authHeader.get(0));

        HttpEntity<CarRequest> request = new HttpEntity<CarRequest>(carRequest, headers);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<CarResponse> carResponse = restTemplate.postForEntity(uri, request, CarResponse.class);

        return cardResponse;
    }
}

However, I am having trouble getting my unit test to work. The below tests throws IllegalArgumentException: URI is not absolute exception:

public class CarServiceTest {

    @InjectMocks
    CarServiceImpl carServiceSut;

    @Mock
    RestTemplate restTemplateMock;

    CardResponse cardResponseFake = new CardResponse();

    @BeforeEach
    void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        cardResponseFake.setCarVin(12345);
    }

    @Test
    final void test_GetCars() {
        // Arrange
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", anyString());

        ResponseEntity<CarResponse> carResponseEntity = new ResponseEntity(carResponseFake, HttpStatus.OK);

        String uri = "http://FAKE/URI/myapi/cars";
        carServiceSut.setUri(uri);

        when(restTemplateMock.postForEntity(
            eq(uri), 
            Mockito.<HttpEntity<CarRequest>> any(), 
            Mockito.<Class<CarResponse>> any()))
        .thenReturn(carResponseEntity);

          // Act
          **// NOTE: Calling this requires real uri, real authentication,
          // real database which is contradicting with mocking and makes
          // this an integration test rather than unit test.**
        ResponseEntity<CarResponse> carResponseMock = carServiceSut.getCar(carRequestFake, headers); 

        // Assert
        assertEquals(carResponseEntity.getBody().getCarVin(), 12345);
    }
}

UPDATE 1

I figured out why the " Uri is not absolute " exection is thrown. It is because in my carService above, I use @Value to inject uri from application.properties file, but in unit tests, that is not injected.

So, I added public property to be able to set it and updated the code above, but then I found that the uri has to be a real uri to a real backend, requiring a real database.

In other words, if the uri I pass is a fake uri, the call to carServiceSut.getCar above, will fail which means this turns the test into an integration test.

This contradicts with using mocking in unit tests. I dont want to call real backend, the restTemplateMock should be mocked and injected into carServiceSut since they are annotated as @Mock and @InjectMock respectively. Therefore, it whould stay a unit test and be isolated without need to call real backend. I have a feeling that Mockito and RestTemplate dont work well together.

I would:

Use constructor injection:

@Service
public class MyServiceImpl implements MyService {

    private String uri;
    
    public MyServiceImpl(@Value("${myUri}") uri) {
        this.uri = uri;
    }

Construct the instance manually in test. Mockito has no support for partial injection.

  • drop @Mock and @InjectMocks
  • drop Mockito.initMocks call
  • use Mockito.mock and constructor in test
public class CarServiceTest {

    public static String TEST_URI = "YOUR_URI";

    RestTemplate restTemplateMock = Mockito.mock(RestTemplate.class);

    CarServiceImpl carServiceSut = new CarServiceImpl(restTemplateMock, TEST_URI):

}

try to change the URI as

String uri = "http://some/fake/url";

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