RestClientResponseException test method call [duplicate] - spring-boot

This question already has answers here:
How do I test a class that has private methods, fields or inner classes?
(58 answers)
Why are my mocked methods not calld when executing a unit test?
(1 answer)
Closed 4 months ago.
I have the following code:
#Component
public class ProductServiceClient {
private final RestTemplate template;
public ProductServiceClient(RestTemplate template) {
this.template = template;
}
public HttpStatus retrieveProducts() {
HttpStatus response = null;
try {
response = template.getForEntity("/products", ProductContainer.class);
} catch (RestClientResponseException exception) {
responseStatus = getAllStat(exception);
}
return response;
}
private HttpStatus getAllStat (RestClientResponseException exception) {
HttpStatus responseStatus = null;
var getResponse = objectMapper.readValue(exception.getResponseBodyAsByteArray(), ProductContainer.class);
responseStatus = getResponse.getCode();
return responseStatus;
}
}
I am having trouble with testing private method getAllstat (it can't be tested directly, only through retrieveProducts() method). I need to cover exactly that method with test. Here what I have by now:
#Test
class GetStatusOfProducts {
RestTemplate template = new RestTemplate();
when(restTemplate.postForEntity("/products", ProductContainer.class).thenThrow(
RestClientResponseException.class);
}

Related

more than one 'primary' service instance suppliers found during load balancing (spring boot/cloud)

I'm currently updating from Spring boot 2.2.x to 2.6.x + legacy code, it's a big jump so there were multiple changes. I'm now running into a problem with load balancing through an api-gateway. I'll apologize in advance for the wall of code to come. I will put the point of failure at the bottom.
When I send in an API request, I get the following error:
more than one 'primary' bean found among candidates: [zookeeperDiscoveryClientServiceInstanceListSupplier, serviceInstanceListSupplier, retryAwareDiscoveryClientServiceInstanceListSupplier]
it seems that the zookeeperDiscovery and retryAware suppliers are loaded through the default serviceInsatnceListSupplier, which has #Primary over it. I thought would take precedence over the other ones. I assume I must be doing something wrong due changes in the newer version, here are the relevant code in question:
#Configuration
#LoadBalancerClients(defaultConfiguration = ClientConfiguration.class)
public class WebClientConfiguration {
#Bean
#Qualifier("microserviceWebClient")
#ConditionalOnMissingBean(name = "microserviceWebClient")
public WebClient microserviceWebClient(#Qualifier("microserviceWebClientBuilder") WebClient.Builder builder) {
return builder.build();
}
#Bean
#Qualifier("microserviceWebClientBuilder")
#ConditionalOnMissingBean(name = "microserviceWebClientBuilder")
#LoadBalanced
public WebClient.Builder microserviceWebClientBuilder() {
return WebClient.builder();
}
#Bean
#Primary
public ReactorLoadBalancerExchangeFilterFunction reactorLoadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
//the transformer is currently null, there wasn't a transformer before the upgrade
return new CustomExchangeFilterFunction(loadBalancerFactory, transformer);
}
}
There are also some Feign Client related configs here which I will omit, since it's not (or shouldn't be) playing a role in this problem:
public class ClientConfiguration {
/**
* The property key within the feign clients configuration context for the feign client name.
*/
public static final String FEIGN_CLIENT_NAME_PROPERTY = "feign.client.name";
public ClientConfiguration() {
}
//Creates a new BiPredicate for shouldClose. This will be used to determine if HTTP Connections should be automatically closed or not.
#Bean
#ConditionalOnMissingBean
public BiPredicate<Response, Type> shouldClose() {
return (Response response, Type type) -> {
if(type instanceof Class) {
Class<?> currentClass = (Class<?>) type;
return (null == AnnotationUtils.getAnnotation(currentClass, EnableResponseStream.class));
}
return true;
};
}
//Creates a Custom Decoder
#Bean
public Decoder createCustomDecoder(
ObjectFactory<HttpMessageConverters> converters, BiPredicate<Response, Type> shouldClose
) {
return new CustomDecoder(converters, shouldClose);
}
#Bean
#Qualifier("loadBalancerName")
public String loadBalancerName(PropertyResolver propertyResolver) {
String name = propertyResolver.getProperty(FEIGN_CLIENT_NAME_PROPERTY);
if(StringUtils.hasText(name)) {
// we are in a feign context
return name;
}
// we are in a LoadBalancerClientFactory context
name = propertyResolver.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
Assert.notNull(name, "Could not find a load balancer name within the configuration context!");
return name;
}
#Bean
public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(
BeanFactory beanFactory, #Qualifier("loadBalancerName") String loadBalancerName
) {
return new CustomRoundRobinLoadBalancer(
beanFactory.getBeanProvider(ServiceInstanceListSupplier.class),
loadBalancerName
);
}
#Bean
#Primary
public ServiceInstanceListSupplier serviceInstanceListSupplier(
#Qualifier(
"filter"
) Predicate<ServiceInstance> filter, DiscoveryClient discoveryClient, Environment environment, #Qualifier(
"loadBalancerName"
) String loadBalancerName
) {
// add service name to environment if necessary
if(environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME) == null) {
StandardEnvironment wrapped = new StandardEnvironment();
if(environment instanceof ConfigurableEnvironment) {
((ConfigurableEnvironment) environment).getPropertySources()
.forEach(s -> wrapped.getPropertySources().addLast(s));
}
Map<String, Object> additionalProperties = new HashMap<>();
additionalProperties.put(LoadBalancerClientFactory.PROPERTY_NAME, loadBalancerName);
wrapped.getPropertySources().addLast(new MapPropertySource(loadBalancerName, additionalProperties));
environment = wrapped;
}
return new FilteringInstanceListSupplier(filter, discoveryClient, environment);
}
}
There was a change in the ExchangeFilter constructor, but as far as I can tell, it accepts that empty transformer,I don't know if it's supposed to:
public class CustomExchangeFilterFunction extends ReactorLoadBalancerExchangeFilterFunction {
private static final ThreadLocal<ClientRequest> REQUEST_HOLDER = new ThreadLocal<>();
//I think it's wrong but I don't know what to do here
private static List<LoadBalancerClientRequestTransformer> transformersList;
private final Factory<ServiceInstance> loadBalancerFactory;
public CustomExchangeFilterFunction (Factory<ServiceInstance> loadBalancerFactory) {
this(loadBalancerFactory);
///according to docs, but I don't know where and if I need to use this
#Bean
public LoadBalancerClientRequestTransformer transformer() {
return new LoadBalancerClientRequestTransformer() {
#Override
public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) {
return ClientRequest.from(request)
.header(instance.getInstanceId())
.build();
}
};
}
public CustomExchangeFilterFunction (Factory<ServiceInstance> loadBalancerFactory, List<LoadBalancerClientRequestTransformer> transformersList) {
super(loadBalancerFactory, transformersList); //the changed constructor
this.loadBalancerFactory = loadBalancerFactory;;
}
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
// put the current request into the thread context - ugly, but couldn't find a better way to access the request within
// the choose method without reimplementing nearly everything
REQUEST_HOLDER.set(request);
try {
return super.filter(request, next);
} finally {
REQUEST_HOLDER.remove();
}
}
//used to be an override, but the function has changed
//code execution doesn't even get this far yet
protected Mono<Response<ServiceInstance>> choose(String serviceId) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerFactory.getInstance(serviceId);
if(loadBalancer == null) {
return Mono.just(new EmptyResponse());
}
ClientRequest request = REQUEST_HOLDER.get();
// this might be null, if the underlying implementation changed and this method is no longer executed in the same
// thread
// as the filter method
Assert.notNull(request, "request must not be null, underlying implementation seems to have changed");
return choose(loadBalancer, filter);
}
protected Mono<Response<ServiceInstance>> choose(
ReactiveLoadBalancer<ServiceInstance> loadBalancer,
Predicate<ServiceInstance> filter
) {
return Mono.from(loadBalancer.choose(new DefaultRequest<>(filter)));
}
}
There were pretty big changes in the CustomExchangeFilterFunction, but the current execution doesn't even get there. It fails here, in .getIfAvailable(...):
public class CustomRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final int DEFAULT_SEED_POSITION = 1000;
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final String serviceId;
private final int seedPosition;
private final AtomicInteger position;
private final Map<String, AtomicInteger> positionsForVersions = new HashMap<>();
public CustomRoundRobinLoadBalancer (
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId
) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(DEFAULT_SEED_POSITION));
}
public CustomRoundRobinLoadBalancer (
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId,
int seedPosition
) {
Assert.notNull(serviceInstanceListSupplierProvider, "serviceInstanceListSupplierProvider must not be null");
Assert.notNull(serviceId, "serviceId must not be null");
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.serviceId = serviceId;
this.seedPosition = seedPosition;
this.position = new AtomicInteger(seedPosition);
}
#Override
// we have no choice but to use the raw type Request here, because this method overrides another one with this signature
public Mono<Response<ServiceInstance>> choose(#SuppressWarnings("rawtypes") Request request) {
//fails here!
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map((List<ServiceInstance> instances) -> getInstanceResponse(instances, request));
}
}
Edit: after some deeper stacktracing, it seems that it does go into the CustomFilterFunction and invokes the constructor with super(loadBalancerFactory, transformer)
I found the problem or a workaround. I was using #LoadBalancerClients because I thought it would just set the same config for all clients that way (even if I technically only have one atm). I changed it to ##LoadBalancerClient and it suddenly worked. I don't quite understand why this made a difference but it did!

Global Exception Handling in Spring Cloud Function on AWS Lambda Platform

I am using spring cloud function on AWS lambda. I am trying to achieve global exception handling like Spring Boot using #ExceptionHandler annotation. But this method is not getting executed and I am getting 500 for any type of exception.
Sample code is below-
#SpringBootApplication
public class App{
public static void main( String[] args ){
SpringApplication.run(App.class, args);
}
#Bean
public Function<Message<User>, User> getUser(){
return (message)->{
User u = message.getPayload();
if(u==null){
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"No user details provided");
}
return u;
}
}
#ExceptionHandler(ResponseStatusException.class)
public APIGatewayProxyResponseEvent handleException(ResponseStatusException e){
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
response.setStatusCode(e.getRawStatusCode());
response.setBody(e.getMessage());
return response;
}
}
I am getting 500 in response instead Bad Request. Is there any way to achieve this scenario ?
You can provide your custom exceptionHandler while building SpringBootLambdaContainerHandler.
public class StreamLambdaHandler implements RequestStreamHandler {
private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
static {
handler = new SpringBootProxyHandlerBuilder<AwsProxyRequest>()
.defaultProxy()
.exceptionHandler(***your customer handler here***)
// other methods are skipped
.buildAndInitialize();
}
}
If you are using spring cloud functions no need to use SpringBootLambdaContainerHandler, what you need to do is create a custom routing function and handle the exception thrown from your lambda function and return APIGatewayProxyResponseEvent. below shows how I achieved the desired result
public class CustomRoutingFunction implements Function<Message<?>, APIGatewayProxyResponseEvent> {
private final FunctionCatalog functionCatalog;
private final FunctionProperties functionProperties;
private final MessageRoutingCallback routingCallback;
public static final String DEFAULT_ROUTE_HANDLER = "defaultMessageRoutingHandler";
public CustomRoutingFunction(FunctionCatalog functionCatalog,
FunctionProperties functionProperties,
MessageRoutingCallback routingCallback) {
this.functionCatalog = functionCatalog;
this.functionProperties = functionProperties;
this.routingCallback = routingCallback;
}
#Override
public APIGatewayProxyResponseEvent apply(Message<?> input) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
try {
String functionDefinition = this.routingCallback.routingResult(input);
SimpleFunctionRegistry.FunctionInvocationWrapper function = functionCatalog.lookup(functionDefinition);
Object output = function.apply(input);
String payload = mapper.writeValueAsString(output);
return new APIGatewayProxyResponseEvent()
.withIsBase64Encoded(false)
.withBody(payload)
.withHeaders(Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE,
"statusCode", "200"))
.withStatusCode(200);
} catch (Exception e) {
return new APIGatewayProxyResponseEvent()
.withIsBase64Encoded(false)
.withHeaders(Map.of(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE,
"statusCode", String.valueOf(HttpStatus.BAD_REQUEST.value())))
.withBody(e.getMessage())
.withStatusCode(HttpStatus.BAD_REQUEST.value());
}
}
}
now you need to register your router function as a bean and pass it to spring.cloud.function.definition
#Bean
public CustomRoutingFunction customRoutingFunction(FunctionCatalog functionCatalog,
FunctionProperties functionProperties,
#Nullable MessageRoutingCallback routingCallback,
#Nullable DefaultMessageRoutingHandler defaultMessageRoutingHandler) {
if (defaultMessageRoutingHandler != null) {
FunctionRegistration functionRegistration = new FunctionRegistration(defaultMessageRoutingHandler, CustomRoutingFunction.DEFAULT_ROUTE_HANDLER);
functionRegistration.type(FunctionTypeUtils.consumerType(ResolvableType.forClassWithGenerics(Message.class, Object.class).getType()));
((FunctionRegistry) functionCatalog).register(functionRegistration);
}
return new CustomRoutingFunction(functionCatalog, functionProperties, routingCallback);
}
inside your application.yml file
spring:
cloud:
function:
definition: customRoutingFunction

406 error always happening with spring rest controller

I'd like to have your opinion on a error always throwed in my spring boot rest controller. I got the following first controller accepting reading requests :
#RequestMapping(value="/read/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity myFunction(#PathVariable("file") String file) {
String[] parts = file.split("\\.");
String extension = parts[1];
List<SousBloc> resWord;
List<SousBloc> resPdf;
List<CvAvecBlocs> resExcel;
RestTemplate rt = new RestTemplate();
rt.getMessageConverters().add(new StringHttpMessageConverter());
if(extension.equals("xlsx")){
resExcel = rt.getForObject("http://localhost:8080/readExcel/"+file, List.class, 200);
return new ResponseEntity<>(resExcel, HttpStatus.OK);
}
else if(extension.equals("pdf")){
resPdf = rt.getForObject("http://localhost:8080/readPdf/"+file, List.class, 200);
return new ResponseEntity<>(resPdf, HttpStatus.OK);
}
else if(extension.equals("docx")){
resWord = rt.getForObject("http://localhost:8080/readWord/"+file, List.class, 200);
return new ResponseEntity<>(resWord, HttpStatus.OK);
}
return null;
}
There is my Reading Word Controller :
#Controller
public class ReadWordController {
private static String UPLOADED_FOLDER = "C:\\cvsUploades\\";
#Autowired
ReadWord readWord;
#RequestMapping(value="/readWord/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity readingWord(#PathVariable("file") String file) throws IOException {
String path = UPLOADED_FOLDER+file;
List<SousBloc> sousBlocs = readWord.extract(path);
return new ResponseEntity<>(sousBlocs, HttpStatus.OK);
}
}
Well this controller works fine and does the job.
Now there is my Reading Pdf Controller :
#Controller
public class ReadPdfController {
private static String UPLOADED_FOLDER = "C:\\cvsUploades\\";
#Autowired
ReadPdf readPdf;
#RequestMapping(value="/readPdf/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity readingPdf(#PathVariable("file") String file) throws IOException {
String path = UPLOADED_FOLDER+file;
List<SousBloc> blocs = readPdf.extract(path);
return new ResponseEntity<>(blocs, HttpStatus.OK);
}
}
It is contructed on the same model of the Reading Word Controller but it does not work. In debug, the program works fine until the return new ResponseEntity<>(blocs, HttpStatus.OK); that throws a 406 error null...
Do you know why ?
EDIT: I tried something strange and it worked... I put the following code :
#Controller
public class ReadWordController {
private static String UPLOADED_FOLDER = "C:\\cvsUploades\\";
#Autowired
ReadWord readWord;
#Autowired
ReadPdf readPdf;
#RequestMapping(value="/readWord/{file:.+}" , method = RequestMethod.GET)
public ResponseEntity readingWord(#PathVariable("file") String file) throws IOException {
/*String path = UPLOADED_FOLDER+file;
List<SousBloc> sousBlocs = readWord.extract(path);
return new ResponseEntity<>(sousBlocs, HttpStatus.OK);*/
String path = "C:\\cvsUploades\\file.pdf";
List<SousBloc> blocs = readPdf.extract(path);
return new ResponseEntity<>(blocs, HttpStatus.OK);
}
}

Spring-boot MultipartFile issue with ByteArrayResource

I'm trying to implement a rest api consuming excel file. I'm using spring-boot and code is available here.
Code works fine when using FileSystemResource for payload. But i'm not able to make the code work with ByteArrayResource in replacement of FileSystemResource:
RestApi.java:
#RestController
public class RestApi {
private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
#PostMapping("/api/upload")
public ResponseEntity<?> uploadFile(#RequestParam("file") MultipartFile uploadfile) {
LOGGER.debug("Single file upload!");
try {
LOGGER.info("\n\n ****** File name: {}, type {}! ************", uploadfile.getOriginalFilename(), uploadfile.getContentType());
this.processExcelFile(uploadfile.getInputStream());
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>("Successfully uploaded - " + uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK);
}
private List<String> processExcelFile(InputStream stream) throws Exception {
List<String> result = new ArrayList<String>();
//Create Workbook instance holding reference to .xlsx file
try(XSSFWorkbook workbook = new XSSFWorkbook(stream);) {
//Get first/desired sheet from the workbook
XSSFSheet sheet = workbook.getSheetAt(0);
//Iterate through each rows one by one
Iterator<Row> rowIterator = sheet.iterator();
while (rowIterator.hasNext()) {
Row row = rowIterator.next();
String cellValue = row.getCell(0).getRichStringCellValue().toString();
result.add(cellValue);
LOGGER.info("\n\n ****** Cell value: {} ************", cellValue);
}
return result;
}
}
}
RestApiTest:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class RestApiTest {
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private ResourceLoader loader;
#Test
public void testUploadFile() throws Exception {
Resource resource = this.loader.getResource("classpath:test.xlsx");
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
// parts.add("file", new FileSystemResource(resource.getFile()));
parts.add("file", new ByteArrayResource(IOUtils.toByteArray(resource.getInputStream())));
String response = this.restTemplate.postForObject("/api/upload", parts, String.class);
Assertions.assertThat(response).containsIgnoringCase("success");
}
}
I'm getting following error when running test:
java.lang.AssertionError:
Expecting:
<"{"timestamp":1487852597527,"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present","path":"/api/upload"}">
to contain:
<"success">
(ignoring case)
Any idea?
when using loader.getResource(...) you must use resource itself as answered above. So you don't need ByteArrayResource. I got this problem, but I'm not using resource from classpath. So if someone really need to use ByteArrayResource, here is my workaround
public class FileNameAwareByteArrayResource extends ByteArrayResource {
private String fileName;
public FileNameAwareByteArrayResource(String fileName, byte[] byteArray, String description) {
super(byteArray, description);
this.fileName = fileName;
}
#Override
public String getFilename() {
return fileName;
}
}
and then use it
parts.add("file", new FileNameAwareByteArrayResource("filename", byteArray));

Spring MVC Test with RestTemplate: Generic collection fails (even with ParameterizedTypeReference)

I am working with Spring Framework 4.3.1
I have the following domain class
#XmlRootElement(name="persona")
#XmlType(propOrder = {"id","nombre","apellido","fecha"})
public class Persona implements Serializable {
#XmlElement(name="id")
#JsonProperty("id")
public String getId() {
return id;
}
....
Where each getter has the #XmlElement and #JsonProperty annotations.
I am working with JAXB2 and Jackson2
I have the following too:
#XmlRootElement(name="collection")
public class GenericCollection<T> {
private Collection<T> collection;
public GenericCollection(){
}
public GenericCollection(Collection<T> collection){
this.collection = collection;
}
#XmlElement(name="item")
#JsonProperty("collection")
public Collection<T> getCollection() {
return collection;
}
public void setCollection(Collection<T> collection) {
this.collection = collection;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
for(Object object : collection){
builder.append("[");
builder.append(object.toString());
builder.append("]");
}
return builder.toString();
}
}
About Testing, the many #Tests methods working through Spring MVC Test work fine. The #Controller and #RestController work how is expected.
Note: I can test the CRUD scenarios, it about the HTTP methods such as POST, PUT, GET and DELETE. Therefore I am able to get one entity and a collection of entities.
Note: from the previous note, all works working around the XML and JSON formats.
Now trying to do testing through the RestTemplate how a kind of programmatic client, it only fails for collections. With the following:
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc));
List<HttpMessageConverter<?>> converters = new ArrayList<>();
converters.add(httpMessageConverterConfig.marshallingMessageConverter());
converters.add(httpMessageConverterConfig.mappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(converters);
System.out.println("converters.size():" + converters.size());
}
I can confirm converters.size() always prints 2
The following is for XML and JSON
#Test
public void findAllXmlTest(){
RequestEntity<Void> requestEntity = RestControllerSupport_.createRequestEntityForGet(uri, retrieveURI);
ParameterizedTypeReference<GenericCollection<Persona>> parameterizedTypeReference = new ParameterizedTypeReference<GenericCollection<Persona>>(){};
ResponseEntity<GenericCollection<Persona>> responseEntity = restTemplate.exchange(requestEntity, parameterizedTypeReference);
assertThat(responseEntity, notNullValue());
assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
assertThat(responseEntity.getHeaders().getContentType(), is(MediaType.APPLICATION_XML) );
assertThat(responseEntity.getBody(), notNullValue());
assertThat(responseEntity.getBody().getClass(), is(GenericCollection.class));
assertThat(responseEntity.getBody().getCollection(), is(personas));
}
#Test
public void findAllJsonTest(){
RequestEntity<Void> requestEntity = RestControllerSupport_.createRequestEntityForGet(uri, retrieveURI);
ParameterizedTypeReference<GenericCollection<Persona>> parameterizedTypeReference = new ParameterizedTypeReference<GenericCollection<Persona>>(){};
ResponseEntity<GenericCollection<Persona>> responseEntity = restTemplate.exchange(requestEntity, parameterizedTypeReference);
assertThat(responseEntity, notNullValue());
assertThat(responseEntity.getStatusCode(), is(HttpStatus.OK));
assertThat(responseEntity.getHeaders().getContentType(), is(MediaType.APPLICATION_JSON_UTF8) );
assertThat(responseEntity.getBody(), notNullValue());
assertThat(responseEntity.getBody().getClass(), is(GenericCollection.class));
assertThat(responseEntity.getBody().getCollection(), is(personas));
}
Note: observe I am using ParameterizedTypeReference for both scenarios.
For JSON it works.
But for XML I get:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [com.manuel.jordan.controller.support.GenericCollection<com.manuel.jordan.domain.Persona>] and content type [application/xml]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
What is wrong or missing?
Your problem that you use MarshallingHttpMessageConverter which isn't GenericHttpMessageConverter, like it is expected for the ParameterizedTypeReference in the HttpMessageConverterExtractor:
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericMessageConverter =
(GenericHttpMessageConverter<?>) messageConverter;
if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
The MappingJackson2HttpMessageConverter is that one.
So, I suggest you to try with Jaxb2CollectionHttpMessageConverter.

Resources