Creating RestTemplate with new RestTemplate vs restTemplateBuilder - java

What is the difference in creating RestTemplate this way
RestTemplate restTemplate = restTemplateBuilder
.setConnectTimeout(Duration.ofMillis(connectTimeout))
.setReadTimeout(Duration.ofMillis(readTimeout))
.build();
and this way
CloseableHttpClient httpClient = HttpClientBuilder.create().disableCookieManagement().build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setReadTimeout(readTimeout);
factory.setConnectTimeout(connectTimeout);
RestTemplate restTemplate = new RestTemplate(factory);
????

I think your question about Scope restTemplateBuilder.As mention in Spring Document:
Scope of restTemplateBuilder
To make the scope of any customizations as narrow as possible, inject
the auto-configured RestTemplateBuilder and then call its methods as
required. Each method call returns a new RestTemplateBuilder instance,
so the customizations only affect this use of the builder.
Example:
private RestTemplate restTemplate;
#Autowired
public HelloController(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
To make an application-wide, additive customization, use a
RestTemplateCustomizer bean. All such beans are automatically
registered with the auto-configured RestTemplateBuilder and are
applied to any templates that are built with it.
Example
static class ProxyCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
HttpHost proxy = new HttpHost("proxy.example.com");
HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
#Override
public HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, request, context);
}
}).build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
}
}
Note: For narrow using RestTemplateBuilder. For application-wide using RestTemplateCustomizer
Reference link: Reference link
Additional detail example: Additional example

Related

Junit for resttemplate in a non spring application

I am working on a non-spring application and using restTemplate feature from spring-web.While writing Junit test i am unable to mock response from restTemplate.postForEntity(). What am i doing wrong here.
Below is the function
public JsonObject apiResponse(Request request) throws JsonProcessingException {
String queryRequest = objectMapper.writeValueAsString(request);
HttpHeaders headers = new HttpHeaders();
headers.add("content-type", "application/json");
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.postForEntity(
"https://testurl/test/",
new HttpEntity<>(queryRequest, headers), String.class);
return JsonParser.parseString(Objects.requireNonNull(responseEntity.getBody())).getAsJsonObject();
Below is the Junit
#ExtendWith(MockitoExtension.class)
public class HandlerTest {
#Spy
#InjectMocks
Handler handler;
#Mock
RestTemplate restTemplate;
#Test
public void apiREsponseTest() throws JsonProcessingException {
//this is not working
Mockito.doReturn(new ResponseEntity <>("test", HttpStatus.OK))
.when(restTemplate).postForEntity(eq("test"), eq(HttpEntity.class), eq(String.class));
assertNotNull(handler.apiResponse(request));
RestTemplate restTemplate = new RestTemplate();
This is not getting mocked as it's a local object and it will always call the real method. You can try this to mock the restTemplate:
RestTemplate restTemplate = getRestTemplate();
protected RestTemplate getRestTemplate() {
return new RestTemplate();
}
//Add this stub to your test
when(handler.getRestTemplate()).thenReturn(restTemplate);
The URL is hardcoded here:
ResponseEntity<String> responseEntity = restTemplate.postForEntity(
"https://testurl/test/",
new HttpEntity<>(queryRequest, headers), String.class);
Hence the you need to change your stubbing as well.
Mockito.doReturn(new ResponseEntity <>("test", HttpStatus.OK))
.when(restTemplate).postForEntity(eq("https://testurl/test/"), eq(HttpEntity.class), eq(String.class));
Making these changes should make your test green hopefully. Let me know if this doesn't resolve your issue.

How to get ClientHttpRequestInterceptor working?

I am trying to get ClientHttpRequestInterceptor working following, Baeldung's Spring RestTemplate Request/Response Logging. The problem is the ClientHttpRequestInterceptor never gets called.
I had a similar issue with a HandlerInterceptor and a HandlerInterceptorAdapter interceptors. The issue was I needed a add a listener, that 99% of the article I found did not mention.
#Configuration
public class ListenerConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext sc) throws ServletException {
sc.addListener(new RequestContextListener());
}
}
I am guessing something about Spring has changed and the listeners are not there by default.
Does anyone know the listener for ClientHttpRequestInterceptor?
The interceptor:
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
static Logger LOGGER = LoggerFactory.getLogger(LoggingInterceptor.class);
#Override
public ClientHttpResponse intercept(
HttpRequest req, byte[] reqBody, ClientHttpRequestExecution ex) throws IOException {
LOGGER.debug("Request body: {}", new String(reqBody, StandardCharsets.UTF_8));
ClientHttpResponse response = ex.execute(req, reqBody);
InputStreamReader isr = new InputStreamReader(
response.getBody(), StandardCharsets.UTF_8);
String body = new BufferedReader(isr).lines()
.collect(Collectors.joining("\n"));
LOGGER.debug("Response body: {}", body);
return response;
}
}
RestClientConfig:
#Configuration
public class RestClientConfig {
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors
= restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new LoggingInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
This may be related to ClientHttpRequestInterceptor not called in springboot.
The Spring RestTemplate Interceptor, only works on the client(consumer) side or in test where the tests are acting as the client where you have a RestTemplate. I am trying to log on the server(producer) side. Thus, no need for a RestTemplate.
A Loredana with Bealdung.com was nice enough to email me and point out, I had a misconception.
Somewhere I had picked up Spring used the RestTemplate for the auto decoding and encoding of POJOs in Rest controllers. That is incorrect.
#Tashkhisi was also point to the lack a RestTempale in the commets.

How to deal with multiple ClientHttpRequestInterceptors in Spring 4

In RestTemplate, I have configured two ClientHttpRequestInterceptor (one for BasicAuthorization and another for Token based authentication.
From client side how I ask RestTemplate to use the correct ClientHttpRequestInterceptor to execute the API call.
Some API calls require BasicAuthorization to work. (For Ex: if the URL starts with "/admin" require BasicAuthorization, others require Token based Authentication)
How I can achieve this in Spring 4?
You could use two instances of RestTemplate, one for Basic auth and one for Token auth.
#Bean
#Qualifier("authRestTemplate")
public RestTemplate getAuthTemplate{
// create rest template, add auth interceptor
}
#Bean
#Qualifier("tokenRestTemplate")
public RestTemplate getTokenTemplate{
// create rest template, add token interceptor
}
Then, when autowiring the RestTemplate, use the desired #Qualifier
#Autowired
#Qualifier("authRestTemplate")
private RestTemplate authTemplate;
#Autowired
#Qualifier("tokenRestTemplate")
private RestTemplate tokenTemplate;
Another option would be adding two ClientHttpRequestInterceptor to the RestTemplate
class BasicAuthInterceptor implements ClientHttpRequestInterceptor {
private final AuthService authService;
public BasicAuthHeaderInterceptor(AuthService authService) {
this.authService = authService;
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
if(isApplicable(request)){
String token = Base64Utils.encodeToString((authService.getUsername() + ":" + authService.getpassword()).getBytes(Charset.forName("UTF-8")));
request.getHeaders().add("Authorization", "Basic " + token);
}
return execution.execute(request, body);
}
}
class TokenInterceptor implements ClientHttpRequestInterceptor {
private final AuthService authService;
public TokenHeaderInterceptor(AuthService authService) {
this.authService = authService;
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
if(isApplicable(request)){
request.getHeaders().add("Authorization", "Bearer " + tokenService.getToken());
}
return execution.execute(request, body);
}
}
Then, add the two interceptors to the RestTemplate
#Bean
public RestTemplate restTemplate(){
RestTemplate template = new RestTemplate();
template.getInterceptors().add(new BasicAuthInterceptor(authService));
template.getInterceptors().add(new TokenInterceptor(authService));
return template;
}

Spring boot - rest template and rest template builder

As I know the RestTemplateBuilder is some kind of factory for RestTemplate. I have a few questions about using it:
Very often in examples there is something like this in #Configuration class:
#Bean
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate();
...
return restClient;
}
Shouldn't RestTemplate be instantiated per #Service class ? If so, how to customize it ?
Spring reference says that RestTemplateBuilder should be customized via RestTemplateCustomizer. How to manage many URI's from many IP addresses with one builder ?
How to add BasicAuthentication globaly to all RestTemplates via RestTemplateBuilder, and is it a good practice?
Thanks for help.
UPDATE:
My application calls rest services from many servers at different IP's and urls - so logically for me is the situation when I have many RestTemplates.
I'm trying to have a factory (RestTemplateBuilder) per server - let's say servers A, B, C. I know how to add a basic authentication. But what for example when I want a basic authentication for server A but not for server B ?
I think about having one RestTemplateBuilder per server. I don't want to do this manually - I would prefer to use Spring mechanisms.
Any help ?
No, you don't need to, typically you will have on rest template instance, and you would pass different url, and request parameters accordingly every time.
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class, vars);
Foo foo = restTemplate.getForObject(fooResourceUrl + "/1", Foo.class);
A descriptive example from spring doc, you can add as many customizers to the builder
public class ProxyCustomizer implements RestTemplateCustomizer {
#Override
public void customize(RestTemplate restTemplate) {
HttpHost proxy = new HttpHost("proxy.example.com");
HttpClient httpClient = HttpClientBuilder.create()
.setRoutePlanner(new DefaultProxyRoutePlanner(proxy) {
#Override
public HttpHost determineProxy(HttpHost target,
HttpRequest request, HttpContext context)
throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, request, context);
}
}).build();
restTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(httpClient));
}
}
Any RestTemplateCustomizer beans will be automatically added to the
auto-configured RestTemplateBuilder. Furthermore, a new
RestTemplateBuilder with additional customizers can be created by
calling additionalCustomizers(RestTemplateCustomizer…​)
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
I've set up my config like this:
#Bean
public RestTemplateCustomizer restTemplateCustomizer() {
return restTemplate -> {
restTemplate.setRequestFactory(clientHttpRequestFactory());
};
}
#Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
clientHttpRequestFactory.setConnectTimeout(connectionTimeoutMs);
clientHttpRequestFactory.setReadTimeout(connectionTimeoutMs);
clientHttpRequestFactory.setBufferRequestBody(false);
return clientHttpRequestFactory;
}
Whenever Spring injects a RestTemplateBuilder, it will configure it using this RestTemplateCustomizer to use the ClientHttpRequestFactory. You may need to do some different customizations, or perhaps none in which case don't declare the bean.
To add the authentication header, you will need to know the user name and password, which you probably won't know until run-time. So I've created an Authenticator bean:
#Component
public class Authenticator {
#Autowired
private RestTemplateBuilder restTemplateBuilder;
public void withAuthenticationHeader(String username, String password, Consumer<RestTemplate> doAuthenticated) {
RestTemplate restTemplate =
restTemplateBuilder
.basicAuthorization(username, password)
.build();
try {
doAuthenticated.accept(restTemplate);
} catch (HttpClientErrorException exception) {
// handle the exception
}
}
}
This allows me to handle authentication failures in a standard way for all requests, which is what I need in my application.
It is injected into other beans and used like so:
#Autowired
private Authenticator authenticator;
public void transmit() {
authenticator.withAuthenticationHeader(username, password, restTemplate ->
restTemplate.postForLocation(url, request));
}
So you'd use the Authenticator rather than using the RestTemple directly.
I couldn't find any standard patterns for this sort of thing, but this seems to work.

Why is my controller sending the content type "application/octet-stream"?

I have a REST controller:
#RequestMapping(value = "greeting", method = RequestMethod.GET, produces = "application/json; charset=utf-8")
#Transactional(readOnly = true)
#ResponseBody
public HttpEntity<GreetingResource> greetingResource(#RequestParam(value = "message", required = false, defaultValue = "World") String message) {
GreetingResource greetingResource = new GreetingResource(String.format(TEMPLATE, message));
greetingResource.add(linkTo(methodOn(AdminController.class).greetingResource(message)).withSelfRel());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<GreetingResource>(greetingResource, responseHeaders, HttpStatus.OK);
}
As you can see, I'm trying hard to specify the content type returned by the controller.
It is accessed with a REST client:
public String getGreetingMessage() {
String message;
try {
HttpHeaders httpHeaders = Common.createAuthenticationHeaders("stephane" + ":" + "mypassword");
ResponseEntity<GreetingResource> responseEntity = restTemplate.getForEntity("/admin/greeting", GreetingResource.class, httpHeaders);
GreetingResource greetingResource = responseEntity.getBody();
message = greetingResource.getMessage();
} catch (HttpMessageNotReadableException e) {
message = "The GET request FAILED with the message being not readable: " + e.getMessage();
} catch (HttpStatusCodeException e) {
message = "The GET request FAILED with the HttpStatusCode: " + e.getStatusCode() + "|" + e.getStatusText();
} catch (RuntimeException e) {
message = "The GET request FAILED " + ExceptionUtils.getFullStackTrace(e);
}
return message;
}
The http headers are created by a utility:
static public HttpHeaders createAuthenticationHeaders(String usernamePassword) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
byte[] encodedAuthorisation = Base64.encode(usernamePassword.getBytes());
headers.add("Authorization", "Basic " + new String(encodedAuthorisation));
return headers;
}
The web security configuration and code work fine. I make sure of this using a mockMvc based integration test which succeeds.
The only test that fails is the one based on the REST template:
#Test
public void testGreeting() throws Exception {
mockServer.expect(requestTo("/admin/greeting")).andExpect(method(HttpMethod.GET)).andRespond(withStatus(HttpStatus.OK));
String message = adminRestClient.getGreetingMessage();
mockServer.verify();
assertThat(message, allOf(containsString("Hello"), containsString("World")));
}
The exception given in the Maven build console output is:
java.lang.AssertionError:
Expected: (a string containing "Hello" and a string containing "World")
got: "The GET request FAILED org.springframework.web.client.RestClientException : Could not extract response: no suitable HttpMessageConverter found for response type [class com.thalasoft.learnintouch.rest.resource.GreetingR esource] and content type [application/octet-stream]\n\tat org.springframework.web.client.HttpMessageConverte rExtractor.extractData(HttpMessageConverterExtract or.java:107)
I'm using the Spring Framework 3.2.2.RELEASE version and the Spring Security 3.1.4.RELEASE version on the Java 1.6 version.
At first, I had a bare bone REST template:
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
I have now added to it, hoping it would help:
private static final Charset UTF8 = Charset.forName("UTF-8");
#Bean
public RestTemplate restTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
messageConverters.add(mappingJackson2HttpMessageConverter);
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(new Class[] {
GreetingResource.class
});
MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
messageConverters.add(marshallingHttpMessageConverter);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new FormHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new BufferedImageHttpMessageConverter());
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
But it didn't change anything and the exception remains the same.
My understanding is that, it is not the REST template that needs any specific JSON configuration, but rather, that, for some reason, my controller is spitting out some application/octet-stream content type instead of some application/json content type.
Any clue?
Some additional information...
The admin rest client bean in the web test configuration:
#Configuration
public class WebTestConfiguration {
#Bean
public AdminRestClient adminRestClient() {
return new AdminRestClient();
}
#Bean
public RestTemplate restTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
messageConverters.add(mappingJackson2HttpMessageConverter);
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(new Class[] {
Greeting.class
});
MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
messageConverters.add(marshallingHttpMessageConverter);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new FormHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new BufferedImageHttpMessageConverter());
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
}
The base test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration( classes = { ApplicationConfiguration.class, WebSecurityConfig.class, WebConfiguration.class, WebTestConfiguration.class })
#Transactional
public abstract class AbstractControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Autowired
protected RestTemplate restTemplate;
protected MockRestServiceServer mockServer;
#Before
public void setup() {
this.mockServer = MockRestServiceServer.createServer(restTemplate);
}
}
The web init class:
public class WebInit implements WebApplicationInitializer {
private static Logger logger = LoggerFactory.getLogger(WebInit.class);
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerListener(servletContext);
registerDispatcherServlet(servletContext);
registerJspServlet(servletContext);
createSecurityFilter(servletContext);
}
private void registerListener(ServletContext servletContext) {
// Create the root application context
AnnotationConfigWebApplicationContext appContext = createContext(ApplicationConfiguration.class, WebSecurityConfig.class);
// Set the application display name
appContext.setDisplayName("LearnInTouch");
// Create the Spring Container shared by all servlets and filters
servletContext.addListener(new ContextLoaderListener(appContext));
}
private void registerDispatcherServlet(ServletContext servletContext) {
AnnotationConfigWebApplicationContext webApplicationContext = createContext(WebConfiguration.class);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(webApplicationContext));
dispatcher.setLoadOnStartup(1);
Set<String> mappingConflicts = dispatcher.addMapping("/");
if (!mappingConflicts.isEmpty()) {
for (String mappingConflict : mappingConflicts) {
logger.error("Mapping conflict: " + mappingConflict);
}
throw new IllegalStateException(
"The servlet cannot be mapped to '/'");
}
}
private void registerJspServlet(ServletContext servletContext) {
}
private AnnotationConfigWebApplicationContext createContext(final Class... modules) {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(modules);
return appContext;
}
private void createSecurityFilter(ServletContext servletContext) {
FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", DelegatingFilterProxy.class);
springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/*");
}
}
The web configuration:
#Configuration
#EnableWebMvc
#EnableEntityLinks
#ComponentScan(basePackages = "com.thalasoft.learnintouch.rest.controller")
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
PageableArgumentResolver resolver = new PageableArgumentResolver();
resolver.setFallbackPageable(new PageRequest(1, 10));
resolvers.add(new ServletWebArgumentResolverAdapter(resolver));
super.addArgumentResolvers(resolvers);
}
}
The application configuration is empty for now:
#Configuration
#Import({ ApplicationContext.class })
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
// Declare "application" scope beans here, that is, beans that are not only used by the web context
}
I had my doubts before, but now that you've posted everything, here's what's up. Assuming the RestTemplate object you use in your getGreetingMessage() method is the same as the one declared in the #Bean method, the problem starts here
this.mockServer = MockRestServiceServer.createServer(restTemplate);
This call overwrites the default ClientHttpRequestFactory object that the RestTemplate object uses internally with a mock. In your getGreetingMessage() method, this call
ResponseEntity<GreetingResource> responseEntity = restTemplate.getForEntity("/admin/greeting", GreetingResource.class, httpHeaders);
doesn't actually go through the network. The RestTemplate uses the mocked ClientHttpRequestFactory to create a fake ClientHttpRequest which produces a fake ClientHttpResponse which doesn't have a Content-Type header. When the RestTemplate looks at the ClientHttpResponse to determine its Content-Type and doesn't find one, it assumes application/octet-stream by default.
So, your controller isn't setting the content type because your controller is never hit. The RestTemplate is using a default content type for your response because it is mocked and doesn't actually contain one.
From your comments:
I wonder if I understand what the mock server is testing. I understand
it is to be used in acceptance testing scenario. Is it supposed to hit
the controller at all ?
The javadoc for MockRestServiceServer states:
Main entry point for client-side REST testing. Used for tests that
involve direct or indirect (through client code)
use of the RestTemplate. Provides a way to set up fine-grained
expectations on the requests that will be performed through the
RestTemplate and a way to define the responses to send back removing
the need for an actual running server.
In other words, it's as if your application server didn't exist. So you could throw any expectations (and actual return values) you wanted and test whatever happens from the client side. So you aren't testing your server, you are testing your client.
Are you sure you aren't looking for MockMvc, which is
Main entry point for server-side Spring MVC test support.
which you can setup to actually use your #Controller beans in an integration environment. You aren't actually sending HTTP request, but the MockMvc is simulating how they would be sent and how your server would respond.
It is bug in MockHttpServletRequest and I will try to describe it.
Issue in tracker https://jira.springsource.org/browse/SPR-11308#comment-97327
Fixed in version 4.0.1
Bug
When DispatcherServlet looking for method to invoke it using some RequestConditions. One of them is ConsumesRequestCondition. The following is a piece of code:
#Override
protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotSupportedException {
try {
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
MediaType.parseMediaType(request.getContentType()) :
MediaType.APPLICATION_OCTET_STREAM;
return getMediaType().includes(contentType);
}
catch (IllegalArgumentException ex) {
throw new HttpMediaTypeNotSupportedException(
"Can't parse Content-Type [" + request.getContentType() + "]: " + ex.getMessage());
}
}
We are interested in piece request.getContentType(). There request is MockHttpServletRequest. Let's look on method getContentType():
public String getContentType() {
return this.contentType;
}
It just return value of this.contentType. It does not return a value from the header! And this.contentType is always NULL. Then contentType in matchMediaType methos will be always MediaType.APPLICATION_OCTET_STREAM.
Solution
I have tried many ways but have found only one that works.
Create package org.springframework.test.web.client in your test directory.
Create copy of org.springframework.test.web.client.MockMvcClientHttpRequestFactory but rename it. For example rename to FixedMockMvcClientHttpRequestFactory.
Find line:
MvcResult mvcResult = MockMvcClientHttpRequestFactory.this.mockMvc.perform(requestBuilder).andReturn();
Replace it with code:
MvcResult mvcResult = FixedMockMvcClientHttpRequestFactory.this.mockMvc.perform(new RequestBuilder() {
#Override
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
MockHttpServletRequest request = requestBuilder.buildRequest(servletContext);
request.setContentType(request.getHeader("Content-Type"));
return request;
}
}).andReturn();
And register your ClientHttpReque
#Bean
public ClientHttpRequestFactory clientHttpRequestFactory(MockMvc mockMvc) {
return new FixedMockMvcClientHttpRequestFactory(mockMvc);
}
I know that it is not beautiful solution but it works fine.

Categories

Resources