Spring TestRestTemplate timeout - spring

When debugging a Spring integration test which uses TestRestTemplate (Note: NOT RestTemplate), I sometimes find the client side of the test times out if I'm stepping through breakpoints on the production code (server side).
How do I change the timeouts for Spring TestRestTemplate?

Add a new config class at the top of your test (I called it TestConfig):
#SpringBootTest(webEnvironment = ..., classes = [YourApplication, TestConfig])
then include that TestConfig class
and include a custom RestTemplateBuilder as follows
These can both be inner classes of your test class if needed
public static class TestConfig {
#Bean
public MyRestTemplateBuilder myRestTemplateBuilder() {
return new MyRestTemplateBuilder()
}
}
public static class MyRestTemplateBuilder extends RestTemplateBuilder {
MyRestTemplateBuilder(RestTemplateCustomizer... customizers) {
super(customizers)
}
#Override
ClientHttpRequestFactory buildRequestFactory() {
ClientHttpRequestFactory result = super.buildRequestFactory()
// Usually result is a: org.springframework.http.client.HttpComponentsClientHttpRequestFactory
result.setConnectTimeout(100000);
result.setReadTimeout(100000);
return result
}
}
Your timeouts will be modified as required.
Bonus:
For normal RestTemplate timeouts you can do:
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// This code can be used to change the read timeout for testing
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
simpleClientHttpRequestFactory.setReadTimeout(100); // millis
return restTemplate;
}

Related

Feign Client Custom Configuration Does Not Take Effect

I use feign in my project. I created a custom configuration but it is overridden by default configuration. Here are the steps:
SpringBootApplication.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableFeignClients
public class MyApplication {}
FeignConfiguration.java
#RequiredArgsConstructor
public class FeignConfiguration{
private final MyConfigurationProperties myConfigProperties;
#Bean
public MetaDataRestClient metaDataRestClient(#Qualifier("metaDataHttpClient") okhttp3.OkHttpClient metaDataHttpClient) {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.client(new OkHttpClient(metaDataHttpClient))
.encoder(new JacksonEncoder(XML_MAPPER))
.decoder(new JacksonDecoder(XML_MAPPER))
.contract(new SpringMvcContract())
.logger(new Slf4jLogger(MetaDataRestClient.class))
.logLevel(Logger.Level.FULL)
.target(MetaDataRestClient.class, myConfigProperties.getMetadata().getEndpoint());
}
#Primary
#Bean(name = "metaDataHttpClient")
public okhttp3.OkHttpClient metaDataHttpClientWithProxy() {
return OkHttpUtil.createNewHttpClientBuilderWithProxy(myConfigProperties.getMetadata().getFeignClient().getConnectTimeout(),
myConfigProperties.getMetadata().getFeignClient().getReadTimeout()).build();
}
MetaDataRestClient.java
#FeignClient(name = "metaDataRestClient", url = "https://myurl.net", configuration = FeignConfiguration.class)
public interface MetaDataRestClient {
#Headers("Content-Type: text/xml")
#GetMapping("/metadata")
public EntityDescriptor getMetadaData();
}
I see that the metaDataRestClient bean is triggered on startup, but when I dig into feign library code, I see that this method is triggered twice: first time with my custom OkHttpClient, and second time with somehing called org.springframework.cloud.sleuth.instrument.web.client.feign.LazyClient. So my custom OkHttpClient is overridden by this lazy client.
Here is the related Feign library code that is being triggered twice:
FeignBuilder.java
public Builder client(Client client) {
this.client = client;
return this;
}
I do not have any feign configurationn in my application.yaml file. What could be the reason for that?
Thanks.

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";

Multiple rest templates in spring boot

I want to configure multiple rest template clients to access different API's. Both are having different authorization headers. I already configured one, Same way configured other rest template too, but that throws error bean 'restTemplate' defined in class path resource .class could not be registered..
#Configuration
public class RestTemplateConfig {
#Autowired
private HeaderRequestInterceptor headerRequestInterceptor;
//constructor
public RestClientConfig() {}
#Bean
public RestTemplate restTemplate( RestTemplateBuilder builder ) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(headerRequestInterceptor));
return restTemplate;
}
}
HeaderRequestInterceptor has base64 encoded authorization, so could not post that code here.
Another RestTemplate:
#Configuration
public class AnotherRestClientConfig {
#Autowired
private AnotherHeaderRequestInterceptor anotherHeaderRequestInterceptor;
#Bean
public RestTemplate restTemplate( RestTemplateBuilder builder ) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(anotherHeaderRequestInterceptor));
return restTemplate;
}
}
Could someone let me know how to configure multiple rest templates in an application.
you could use #Qualifier as mentioned by #VirtualTroll. Or create a specific client bean per api and hold the restemplate instance there.
#Component
public class ApiClient1 {
private final RestTemplate customRestTemplate;
public ApiClient1() {
this.customRestTemplate = ...
}
public void useApi() {
}
}

mock rest template for unit test

I want to mock a RestTemplate in Spring Boot, where I'm making a REST call within a method. To test the controller of the microservice I'm creating,
I want to test methods inside the controller of my micro service.
For example:
#GetMapping(value = "/getMasterDataView", produces = { MediaType.APPLICATION_JSON_VALUE })
#CrossOrigin(origins = { "http://192.1**********" }, maxAge = 3000)
public ResponseEntity<MasterDataViewDTO> getMasterDataView() throws IOException {
final String uri = "http://localhost:8089/*********";
RestTemplate restTemplate = new RestTemplate();
MasterDataViewDTO masterDataViewDTO = restTemplate.getForObject(uri, MasterDataViewDTO.class);
return new ResponseEntity<>(masterDataViewDTO, HttpStatus.OK);
}
how to I test this using mocking?
This is what I have so far:
#Test
public void testgetMasterDataView() throws IOException {
MasterDataViewDTO masterDataViewDTO= mock(MasterDataViewDTO.class);
//String uri = "http://localhost:8089/*********";
Mockito.when(restTemplate.getForObject(Mockito.anyString(),ArgumentMatchers.any(Class.class))).thenReturn(masterDataViewDTO);
assertEquals("OK",inquiryController.getMasterDataView().getStatusCode());
}
I am getting an error when I'm running the mock, the method getMasterDataView() is getting called and the REST call within it is also getting called and is throwing an error. How can I write my test so that the REST endpoint is not called? If it's possible, I'd like to do this with Mockito.
Before you start writing a test, you should change your code a bit. First of all, it would be a lot easier if you extracted that RestTemplate, and created a separate bean for it which you would inject within your controller.
To do that, add something like this within a #Configuration class or within your main class:
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
Additionally, you have to remove the new RestTemplate() from your controller, and autowire it in stead, for example:
#Autowired
private RestTemplate restTemplate;
Now that you've done that, it's going to be a lot easier to inject a mock RestTemplate within your tests.
For your testing, you have two options:
Either mock RestTemplate and all the methods you are trying to access, using a mocking framework (eg. Mockito)
Or you can use MockRestServiceServer, which allows you to write tests that verify if the URLs are properly called, the request matches, and so on.
Testing with Mockito
To mock your RestTemplate with Mockito, you have to make sure that you add the following annotation to your tests:
#RunWith(MockitoJUnitRunner.class)
After that, you can do this:
#InjectMocks
private MyController controller;
#Mock
private RestTemplate restTemplate;
And now you can adjust your tests like this:
#Test
public void testgetMasterDataView() throws IOException {
MasterDataViewDTO dto = new MasterDataViewDTO();
when(restTemplate.getForObject("http://localhost:8089/*********", MasterDataViewDTO.class)).thenReturn(dto);
ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo(dto);
}
You could mock the DTO as you did within your test, but you don't have to, and I don't think there's any benefit from doing so. What you do have to mock is the restTemplate.getForObject(..) call.
Testing with MockRestServiceServer
Another approach is to use MockRestServiceServer. To do that, you have to use the following annotations for your test:
#RunWith(SpringRunner.class)
#RestClientTest
And then you'll have to autowire your controller and MockRestServiceServer, for example:
#Autowired
private MyController controller;
#Autowired
private MockRestServiceServer server;
And now you can write tests like this:
#Test
public void testgetMasterDataView() throws IOException {
server
.expect(once(), requestTo("http://localhost:8089/*********"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(new ClassPathResource("my-mocked-result.json"), MediaType.APPLICATION_JSON));
ResponseEntity<MasterDataViewDTO> response = controller.getMasterDataView();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
// TODO: Write assertions to see if the DTO matches the JSON structure
}
In addition to testing that your actual REST call matches, this also allows you to test if your JSON-to-DTO works as well.
You can achieve this by using #RestClientTest and MockRestServiceServer. An example provided in their documentation:
#RunWith(SpringRunner.class)
#RestClientTest(RemoteVehicleDetailsService.class)
public class ExampleRestClientTest {
#Autowired
private RemoteVehicleDetailsService service;
#Autowired
private MockRestServiceServer server;
#Test
public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails()
throws Exception {
this.server.expect(requestTo("/greet/details"))
.andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
String greeting = this.service.callRestService();
assertThat(greeting).isEqualTo("hello");
}
}
create bean instead of using new RestTemplate() in your services.
use Profile("!test") for your bean that use only for non test profile.
create test class like this:
#SpringBootTest(properties = "spring.profiles.active:test")
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#Log4j2
#Transactional
public class QabzinoMockTest {
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private QabzinoLogRepository qabzinoLogRepository;
private MockMvc mockMvc;
private final WebServiceStatus successStatus = new WebServiceStatus();
#Mock
private RestTemplate restTemplate;
private final Gson mapper = new Gson();
#TestConfiguration
static class Config {
#Bean
public RestTemplate rest() {
return Mockito.mock(RestTemplate.class);
}
}
#Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
this.restTemplate = (RestTemplate) this.webApplicationContext.getBean("rest");
LoginOutput responseModel = new LoginOutput();
responseModel.setStatus("200");
Mockito.when(
restTemplate.postForEntity(
Mockito.eq(LOGIN_URL),
Mockito.isA(HttpEntity.class),
Mockito.eq(String.class)
)
).thenReturn(ResponseEntity.ok().body(mapper.toJson(responseModel)));
}
}
in this sample we create bean with rest name in static class Config and mock it in setup method Before all tests.
and preferred test config maybe useful for you application-test.properties:
spring.h2.console.enabled=true
spring.jpa.defer-datasource-initialization=true
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE;IGNORECASE=TRUE;
spring.datasource.username=sa
spring.datasource.password=sa
and finally when we see restTemplate in our code, mock bean return our responseModel instead of real service call :)

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