406 error while mocking File Download REST API using MockMVC - spring

I am implementing a REST API using Spring framework, which returns
return new ResponseEntity<>(new InputStreamResource(myInputStream),
responseHeaders, HttpStatus.OK);
REST API is declared as:
#RequestMapping(value = "/download", method = RequestMethod.GET,
produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE })
While writing unit test for this API, I am using MockMVC like below:
final MappingJackson2HttpMessageConverter messageConverter =
new MappingJackson2HttpMessageConverter();
messageConverter.setObjectMapper(new ObjectMapper());
messageConverter.getObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
this.mockMvc =
MockMvcBuilders.standaloneSetup(myController)
.setMessageConverters(messageConverter)
.apply(new RestDocumentationConfigurer()
.withScheme("https").withHost("localhost")
.withPort(443)).build();
And my Test case looks like this:
mockMvc.perform(
org.springframework.test.web.servlet.request.MockMvcRequestBuilders
.get(restUri))
.andExpect(
org.springframework.test.web.servlet.result.MockMvcResultMatchers
.status().isOk())
.andDo(document("myApi")).andReturn();
But I am getting status as error 406.
java.lang.AssertionError: Status expected:<200> but was:<406>
at org.springframework.test.util.AssertionErrors.fail
What I am missing here?
Any help would be much appreciated.

You inject instance of MappingJackson2HttpMessageConverter to MockMvcBuilders which can't deal with converting classes inheriting from Resource. All you need to do is add ResourceHttpMessageConverter to your test specification:
MockMvcBuilders.standaloneSetup(myController)
.setMessageConverters(messageConverter, new ResourceHttpMessageConverter())

Status code 406 means "Not Acceptable", which indicates that the server is missing a header specifying an accepted content type. You'll need to include that in your mockMvc call.

Related

How to do java unit test with protobuf for controller?

I have a spring boot rest controller with requestBody & responseBody both protobuf. like below :
#RequestMapping(value = "/position/open", produces = "application/x-protobuf")
#ResponseBody
public MsgProto.Response positionOpen(#RequestBody MsgProto.Request request)throws Exception {
log.info("start /position/open");
return orderPositionService.addOrder(request);
}
Now I want to do a unit test using mockMvc to test the controller, but it failed every time. I believe it is the code below which is wrong to fire an HTTP request with protobuf, any idea how to resolve it?
mockMvc.perform(post("/position/open").contentType("application/x-protobuf")
.content(ObjectsMock.mockMsgProtoRequest().toByteArray())).andDo(print())
.andExpect(status().isOk());
Exception :
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
MockHttpServletResponse:
Status = 415
Error message = null
Headers = [Accept:"application/json, application/octet-stream,
application/xml, application/*+json, text/plain, text/xml, application/x-www-
form-urlencoded, application/*+xml, multipart/form-data, multipart/mixed, */*"]
I assume the ProtobufHttpMessageConverter is missing here. Spring MVC can't read/write any messages without this specific converter.
You can create it as the following:
#Bean
public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
Next, make sure to add the HTTP Method to your method, as I assume (from reading your test) you want this to be a HTTP POST handler. You can also add the consumes attribute to state that this endpoint also consumes Protobuf.
#RequestMapping(method = RequestMethod.POST, consumes = "application/x-protobuf", value = "/position/open", produces = "application/x-protobuf")
#ResponseBody
public MsgProto.Response positionOpen(#RequestBody MsgProto.Request request)throws Exception {
log.info("start /position/open");
return orderPositionService.addOrder(request);
}
In addition to this, there is an article on the Spring blog available that covers your usecase and explains how to use Protobuf with Spring MVC.
You need to add Protobuf converter to MockMvc builder
MockMvcBuilders.standaloneSetup(controller)
.setMessageConverters(new ProtobufHttpMessageConverter())
.build()
This fixed the issue for me

Unit test MockHttpServletRequest not returning content type

I would like the application to return JSON object from my Java Classes (both success and fail cases).
I had defined a #RestControllerAdvice to handle the errors from controller. My program also shows the error message correctly in json, but the problem is in the unit test.
The problem is when it throws:
org.springframework.web.bind.MethodArgumentNotValidException
My unit test failed with error :
java.lang.AssertionError: Response header 'content-type' expected:<application/json;charset=UTF-8> but was:<null>
Controller:
#PostMapping("/import")
public ResponseEntity<StatusModel> import(#Valid #RequestBody ImportModel importModel ){
//logic
return new ResponseEntity<>(new StatusModel("Data accepted."), HttpStatus.OK);
}
Unit Test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {MockConfiguration.class})
#WebAppConfiguration
public class ModelControllerTest {
private MockMvc mockMvc;
#InjectMocks
private ModelController controller;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void import_validRequest_imported() throws Exception {
mockMvc
.perform(
post("/import")
.content(VALID_CONTENT).contentType("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andExpect(header().string("content-type", "application/json;charset=UTF-8"))
.andExpect(jsonPath("$.status", equalTo("Data accepted")));
}
#Test
public void import_invalidRequest_notImported() throws Exception {
mockMvc
.perform(
post("/import")
.content(INVALID_CONTENT).contentType("application/json"))
.andExpect(status().isBadRequest())
.andDo(print())
.andExpect(header().string("content-type", "application/json")); <----- This assertion failed
}
}
MockHttpServletRequest log:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /import
Parameters = {}
Headers = {Content-Type=[application/json]}
Handler:
Type = com.test.ModelController
Method = public org.springframework.http.ResponseEntity<com.model.StatusModel> com.ModelController.import(com.test.model.ImportModel)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.bind.MethodArgumentNotValidException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Why is the content type, error message is empty?
Here is the rational why mock mvc doesn't support spring boot exception handlers followed by recommendation and fix.
Rational excerpt
Spring Boot's error handling is based on Servlet container error
mappings that result in an ERROR dispatch to an ErrorController.
MockMvc however is container-less testing so with no Servlet container
the exception simply bubbles up with nothing to stop it.
So MockMvc tests simply aren't enough to test error responses
generated through Spring Boot. I would argue that you shouldn't be
testing Spring Boot's error handling. If you're customizing it in any
way you can write Spring Boot integration tests (with an actual
container) to verify error responses. And then for MockMvc tests focus
on fully testing the web layer while expecting exceptions to bubble
up.
This is a typical unit vs integration tests trade off. You do unit
tests even if they don't test everything because they give you more
control and run faster.
Recommendation excerpt
How can we write tests for error conditions using default spring-boot
JSON responses, then?
#xak2000 Rossen's already covered this, but I wanted to give you a
direct answer. If you really want to test the precise format of the
error response then you can use an integration test using
#SpringBootTest configured with a DEFINED_PORT or RANDOM_PORT web
environment and TestRestTemplate.
Complete details here
https://github.com/spring-projects/spring-boot/issues/7321
Fix
Here is slightly different error validation using Spring Boot test.
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest(classes = DemoApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ModelControllerTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
void import_invalidRequest_notImported() throws JSONException {
String expected = "{\"status\":400,\"error\":\"Bad Request\",\"message\":\"JSON parse error: Unrecognized token 'Invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'Invalid': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\\n at [Source: (PushbackInputStream); line: 1, column: 8]\",\"path\":\"/import\"}";
String invalidJson = "Invalid";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(invalidJson, headers);
ResponseEntity<String> response = restTemplate.exchange("/import", HttpMethod.POST, entity, String.class);
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
JSONAssert.assertEquals(expected, response.getBody(), false);
}
}
Reference here
https://mkyong.com/spring-boot/spring-rest-integration-test-example/

How do I make spring boot RestTemplate client metrics not create a new tag for query parameters

I've got a spring boot application that is defining a RestTemplate bean as follows:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
Also, pulling in spring-boot-starter-actuator and io.micrometer:micrometer-registry-prometheus.
When I use the injected RestTemplate as follows:
#Autowired
private RestTemplate restTemplate;
private String uriTemplate = "http://my.domain.com/bookstore-api/books";
public List<Book> getBooksByAuthor(String author) {
// create URI for "http://my.domain.com/bookstore-api/books?author={authorId}"
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
.fromUriString(uriTemplate)
.queryParam("author", author);
// make the GET
ResponseEntity<Book[]> responseEntity = restTemplate.getForEntity(uriComponentsBuilder.toUriString(),Book[].class);
// rest ommitted for brevity
}
When getBooksByAuthor("Tolkien") is called, we can then hit /metrics/prometheus and see the following:
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 0.253227898
This would be fine, except that there are lots of authors out there, and eventually I will get the "too many tags" exception.
I would prefer to have the following (similar to how path variables get templated):
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 0.253227898
Is this possible to achieve by modifying the way I use UriComponentsBuilder? The closest thing I've found is to define my own RestTemplateExchangeTagsProvider, and override the default to do some crazy regex replacement.
Just fixed same issue in SpringBoot 2.4.5 using:
responseEntity = restTemplate.exchange(
config.getDataUrl(),
HttpMethod.GET,
httpEntity,
new ParameterizedTypeReference<String>() {},
rowId);
where getDataUrl resolves to:
https://data-service-dev.apps.cloud.net/api/hbase/getData?rowId={rowId}
metrics:
http_client_requests_seconds_count{clientName="data-service-dev.apps.cloud.net",method="GET",outcome="SUCCESS",status="200",uri="/api/hbase/getData?rowId={rowId}",} 1.0
...
I have had a same problem. Maybe this information will help you.
In my case restTemplate.setUriTemplateHandler(handler) had overwritten annonymous
MetricsClientHttpRequestInterceptor#createUriTemplateHandler.
And the original url templates had not been stored into memory for prometheus.
DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
builderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
customizer.customize(restTemplate);
restTemplate.setUriTemplateHandler(handler);
So,
I changed order of the commands:
restTemplate.setUriTemplateHandler(handler);
customizer.customize(restTemplate);
Please check that there are no setting commands for restTemplate after MetricsClientHttpRequestInterceptor.customize(restTemplate).

How to document error messages using Spring Rest Doc

I'm using Spring rest doc along spring mvc test to generate a restful documentation. Now I'm trying to describe the possible error messages on the resource, but I cannot find anything that will help me in the spring documentation.
What I'm trying to achieve is similar to Error 4xx section of http://apidocjs.com/example/
Any thoughts ?
Just create a test that intentionally generates an error response. And document the fields like you would with any other Spring Rest Docs test.
#Test
public void errorExample() throws Exception {
RestDocumentationResultHandler docs = document("Error Response",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
responseFields(
fieldWithPath("status").optional().type("Integer").description("Application status field."),
fieldWithPath("errorMsg").type("String").description("A global description of the cause of the error")
)
);
AppConfig req = getAppConfigReq(null, null, null);
ResultActions result = this.mockMvc.perform( post("/SomeAPICall/")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(this.objectMapper.writeValueAsString(req))
);
result.andExpect(status().is(401));
result.andDo(docs);
}

Mocktesting RestTemplate using Mockito in Spring boot latest version

I am using 1.3.3.RELEASE version of Spring Boot.
I am trying to unit test my service which uses RestTemplate to call a url for fetching html page.
This is the code
Mockito.when(restTemplate.exchange(
Mockito.anyString(),
Mockito.any(HttpMethod.class),
Mockito.any(HttpEntity.class),
Mockito.any(Class.class)))
.thenReturn(new ResponseEntity<String>("",
new HttpHeaders(), HttpStatus.OK));
But thenReturn part is having issues. What could be the right construction of ResponseEntity for getting an html page.
Appreciate any help.
Thanks
you need to mock out responseEntity
so:
ResponseEntity<String> mockResponse = mock(ResponseEntity.class);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
then like you did before:
when(mockRestTemplate.exchange(anyString(), anyObject(), anyObject(), eq(String.class)))
.thenReturn(mockResponse);

Resources