Spring RestTemplateBuilder does not add MessageConverters - spring

I created this restTemplate bean..
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.messageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()))
.build();
}
as you can see i set the messageConverter.
but when i used the bean inside my service class ... i get an error like this
"RestClientException: Could not write request: no suitable HttpMessageConverter found for request type . . "
here is the code in my service class . .
ResponseEntity<ProcessGroupEntity> response = this.restTemplate.exchange(RequestEntity.post(new URI(uri))
.contentType(MediaType.APPLICATION_JSON)
.body(foo)
, Foo.class);
now i tried setting message converter before calling exchange.
this.restTemplate.setMessageConverters(Collections.singletonList(new MappingJackson2HttpMessageConverter()));
ResponseEntity<ProcessGroupEntity> response = this.restTemplate.exchange(RequestEntity.post(new URI(uri))
.contentType(MediaType.APPLICATION_JSON)
.body(foo)
, Foo.class);
and the code works. so my question is why RestTemplateBuilder's messageConverter() method seems to fail in setting/adding the converter.?
edit:
here is how i inject the restTemplate bean in my service class
private final RestTemplate restTemplate;
...
#Autowired
public SomeService(RestTemplate restTemplate,
...) {
this.restTemplate = restTemplate;
}

Related

Using #InjectMock giving URI not absolute error

I have written following Junit for the method which is using rest template to call another service but the mocking is throwing URI not absolute error.
Method:
#Value("${app.prop.esso-url}")
private String essoUrl;
public ResponseEntity<String> essoCall(String token) {
ResponseEntity<String> response=null;
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
headers.setBearerAuth(token);
HttpEntity<String> entity = new HttpEntity<>(headers);
response= restTemplate.exchange(essoUrl, HttpMethod.GET, entity,String.class);
logger.info("successfully received response.");
return response;
}
Junit:
#Autowired
private ObjectMapper objectMapper;
#InjectMocks
SecurityMsServiceImpl securityservice=new SecurityMsServiceImpl();
#Value("${app.prop.esso-url}")
private String essoUrl;
#Mock
private RestTemplate restTemplate;
#Test
void givenMockingIsDoneByMockito_whenGetIsCalled_shouldReturnMockedObject() {
ResponseEntity<String> responseEntity = new ResponseEntity<String>("success", HttpStatus.OK);
Mockito
.when(restTemplate.getForEntity(essoUrl
, String.class))
.thenReturn(responseEntity);
ResponseEntity<String> finalresponse=securityservice.essoCall("abc");
assertEquals(responseEntity, finalresponse);
}
I see this problem time and time again:
You are constructing new instance of RestTemplate in your method under test.
What you should do instead is to inject it into your bean, which means:
add a field of type RestTemplate in your bean
initialize it in constructor
in tests, pass in mocked instance
in prod, pass in a regular instance via Spring DI
RestTemplate is thread-safe and one instance of it is perfectly valid.
See Is RestTemplate thread safe?
On top of that:
my advice is to use constructor injection instead of field injection
you are mixing Spring's #Autowired with Mockito's #InjectMocks. Choose if you want to test with MockitoExtension or SpringExtension and pick the appropriate set of annotations to initialize your SUT.

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.
You need to construct your system under test properly.
Currently, MyServiceImpl.uri is null.
More importantly, your mock of RestTemplate is not injected anywhere, and you construct a new RestTemplate in method under test.
As Mockito has no support for partial injection, you need to construct the instance manually in test.
I would:
Use constructor injection to inject both restTemplate and uri:
#Service
public class MyServiceImpl implements MyService {
private RestTemplate restTemplate;
private String uri;
public MyServiceImpl(RestTemplate restTemplate, #Value("${myUri}") uri) {
this.restTemplate = restTemplate;
this.uri = uri;
}
Construct the instance manually:
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):
}
Remove creation of restTemplate in method under test.
If needed, add a config class providing RestTemplate bean (for the application, the test does not need that):
#Configuration
public class AppConfig {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Note that RestTemplate is thread-safe, one instance per app is enough: Is RestTemplate thread safe?
try to change the URI as
String uri = "http://some/fake/url";

Getting the request URL of a SOAP request in handleResponse Method in ClientInterceptor

I am using spring boot. I have written a ClientInterceptor for a SOAP web service and i would like to log the URL that i am sending my request to, and i would like to do it in the handleResponse method. However i could not find a way to do it. Is it possible? Any help would be great.
public class SoapClientHttpRequestInterceptor implements ClientInterceptor {
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
// I would like to get the URL and log it here.
}
}
The way i create the restTemplate
#Bean
#Qualifier("testRestTemplate")
public RestTemplate testRestTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setOutputStreaming(false);
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(requestFactory);
RestTemplate restTemplate = new RestTemplate(factory);
restTemplate.setInterceptors(Collections.singletonList(restClientHttpRequestInterceptor));
restTemplate.setErrorHandler(testErrorHandler);
restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
You can do this in your handleResponse method:
TransportContext context = TransportContextHolder.getTransportContext();
context.getConnection().getUri().toString()
i hope this helps you

Spring Boot RestTemplate Basic Authentication using RestTemplateBuilder

In Spring Boot I'm trying to create a RestTemplate which will use basic authentication using
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
builder.basicAuthorization("username", "password");
RestTemplate template = builder.build();
return template;
}
I then inject the RestTemplate in my service class as
#Autowired
private RestTemplate restTemplate;
However, my requests fail with a 401 unauthorized exception:
Caused by: org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
Using another REST Client (Postman) the requests to the same URL succeeds so I assume the basic authentication is not working correctly. From the debug output it looks as if the authentication header is not being set. What will make this work?
The problem is that you are using the RestTemplateBuilder in a wrong way. The RestTemplateBuilder is immutable. So when doing builder.basicAuthorization("username", "password") you actually get a new instance, with a BasicAuthorizationInterceptor added and configured, of the RestTemplateBuilder. (this applies to all configuration methods of the RestTemplateBuilder they all create a fresh copied instance).
However your code is discarding that specifically configured instance and you are basically using the non secured default RestTemplateBuilder.
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
builder.basicAuthorization("username", "password");
RestTemplate template = builder.build();
return template;
}
This code should be replaced with something like this.
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.basicAuthorization("username", "password").build();
}
Which will use the specifically configured instance.
One solution is to create the RestTemplate as follows:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate template = builder.build();
template.setMessageConverters(
Arrays.asList(
new FormHttpMessageConverter(),
new StringHttpMessageConverter()
)
);
template.getInterceptors().add(new BasicAuthorizationInterceptor("username", "password"));
return template;
}

How to Create or configure Rest Template using #Bean in Spring Boot

I want to define RestTemplate as an application bean using #Bean annotation in my configuration class in a spring boot application.
I am calling 4 rest services in different places in my application flow. Currently I am creating RestTemplate every time every request. Is there a way I can define that as application bean using #Bean and inject that using #Autowired?
Main reason for this question is I can able to define RestTemplate using #Bean but when I inject it with #Autowired I am loosing all defined interceptors (Interceptors are not getting called.)
Configuration Class
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new RestServiceLoggingInterceptor());
restClient.setInterceptors(interceptors);
return restClient;
}
Service Class
public class MyServiceClass {
#Autowired
private RestTemplate appRestClient;
public String callRestService() {
// create uri, method response objects
String restResp = appRestClient.getForObject(uri, method, response);
// do something with the restResp
// return String
}
}
It seems my Interceptors are not getting called at all with this configuration. But RestTemplate is able to make a call to the REST service and get a response.
Answer for Spring boot 2.*.* version.
I am using Spring boot 2.1.2.RELEASE and I also added RestTemplate in my project in a class where mail method exists.
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.setConnectTimeout(Duration.ofMillis(300000))
.setReadTimeout(Duration.ofMillis(300000)).build();
}
and Used in my service or other classes like this
#Autowired
RestTemplate res;
and in methods
HttpEntity<String> entity = new HttpEntity<>(str, headers);
return res.exchange(url, HttpMethod.POST, entity, Object.class);
Judging form the name of the interceptor, I'm guessing you're doing some logging in it? You could of missed logging level configuration. I created a small application to check weather your configuration works, using 1.3.6.RELEASE version.
In this class I define the RestTemplate bean and the interceptor with logging.
package com.example;
// imports...
#SpringBootApplication
public class TestApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(TestApplication.class);
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
// Add one interceptor like in your example, except using anonymous class.
restClient.setInterceptors(Collections.singletonList((request, body, execution) -> {
LOGGER.debug("Intercepting...");
return execution.execute(request, body);
}));
return restClient;
}
}
For logging to work, I also have to set the correct debug level in application.properties.
logging.level.com.example=DEBUG
Then I create a service where I inject this RestTemplate.
#Service
public class SomeService {
private final RestTemplate appRestClient;
#Autowired
public SomeService(#Qualifier("appRestClient") RestTemplate appRestClient) {
this.appRestClient = appRestClient;
}
public String callRestService() {
return appRestClient.getForObject("http://localhost:8080", String.class);
}
}
And also an endpoint to test this out.
#RestController
public class SomeController {
private final SomeService service;
#Autowired
public SomeController(SomeService service) {
this.service = service;
}
#RequestMapping(value = "/", method = RequestMethod.GET)
public String testEndpoint() {
return "hello!";
}
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
return service.callRestService();
}
}
By performing a GET request to http://localhost:8080/test I should expect to get the String hello! getting printed (the service makes a call to http://localhost:8080 which returns hello! and sends this back to me). The interceptor with logger also prints out Intercepting... in the console.
Edd's solution won't work if you're using Spring Boot 1.4.0 or later. You will have to use RestTemplateBuilder to get this working. Here is the example
#Bean(name="simpleRestTemplate")
#Primary
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
RestTemplate template = restTemplateBuilder.requestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
.interceptors(logRestRequestInterceptor) //This is your custom interceptor bean
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
return template;
}
Now you can autowire the bean into your service class
#Autowired
#Qualifier("simpleRestTemplate")
private RestTemplate simpleRestTemplate;
Hope this helps

Resources