Affects: 5.3.8
During an upgrade from springboot 2.3.8.RELEASE to 2.5.8 I have encountered some issues with integration testing during assertions to the request contentType. Example of assertion:
assertEquals(MediaType.APPLICATION_JSON_VALUE, response.getContentType());
I noticed that this commit was made to fix some issues the community had pointed out https://github.com/spring-projects/spring-framework/commit/e4b9b1fadb35c4e439acc8931910aaba6e1342e3 But I've been wondering if there is a way to get the contentType alone from the request Currently
private void updateContentTypePropertyAndHeader() {
if (this.contentType != null) {
String value = this.contentType;
if (this.characterEncodingSet && !value.toLowerCase().contains(CHARSET_PREFIX)) {
value += ';' + CHARSET_PREFIX + getCharacterEncoding();
this.contentType = value;
}
doAddHeaderValue(HttpHeaders.CONTENT_TYPE, value, true);
}
}
https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java#L219 Appends the character encoding to the contentType, therefore the assertion in the integration fails like so:
org.opentest4j.AssertionFailedError:
Expected :application/json
Actual :application/json;charset=UTF-8
I tried setting a default character encoding for mockMvc
@Bean
public MockMvc mockMvc() {
return MockMvcBuilders.webAppContextSetup(webApplicationContext)
.defaultResponseCharacterEncoding(Charset.defaultCharset()).build();
}
Which I thought would help me avoid setting the flag
this.characterEncoding = true
https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java#L212
But that was unsuccessful as AbstractJackson2View#prepareResponse sets the character encoding triggering the flag to be set to true, which in turn appends the encoding to the contentType
https://github.com/spring-projects/spring-framework/blob/5.3.x/spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java#L150
So I guess the question is, am I missing something? It seems like there is no way to get the respose.contentType by itself (without character encoding). If so, should MediaType.APPLICATION_JSON_UTF8_VALUE be deprecated? https://github.com/spring-projects/spring-framework/blob/5.3.x/spring-web/src/main/java/org/springframework/http/MediaType.java#L118 It seems like there is an inconsistency here between spring-web and spring-test. Are there better ways to test this? Or a way to disable the character encoding from getting appended to the contentType?
Comment From: sbrannen
Are there better ways to test this?
Are you familiar with org.springframework.test.web.servlet.result.ContentResultMatchers.contentTypeCompatibleWith(MediaType)
and org.springframework.http.MediaType.isCompatibleWith(MediaType)
?
Comment From: fabianlem
@sbrannen thanks for the quick reply This is what I'm testing:
assertEquals(MediaType.APPLICATION_JSON_VALUE, response.getContentType());
Sol 1: org.springframework.test.web.servlet.result.ContentResultMatchers.contentTypeCompatibleWith(MediaType)
Works!
MvcResult result = mockMvc.perform(get("/{foo}/bar", "foo")
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andReturn();
Sol 2: org.springframework.http.MediaType.isCompatibleWith(MediaType)
Works!
assertTrue(MediaType.parseMediaType(response.getContentType()).isCompatibleWith(MediaType.APPLICATION_JSON));
//OR
assertTrue(MediaType.APPLICATION_JSON.isCompatibleWith(MediaType.parseMediaType(response.getContentType())));
Some thoughts
Although both solutions work, there is a lot of processing going on to get the MimeType from the response contentType https://github.com/spring-projects/spring-framework/blob/5.3.x/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java#L198
it may be more performant to do something like this:
assertTrue(request.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)
Though both solutions work it seems like there is too much overhead to get the contentType
, because we already had it in the response at some point. I wonder if appending the character encoding by default is the most appropriate approach?
Getting the contentType from the response header gives the same concatenated contentType.
I would advocate for a way to retrieve the contentType by itself as it was previously possible.
I think a better solution would be to append the character encoding in this method: MockHttpServletRequest.getContentTypeAsString
As it stands the current implementation of Request#getContentType
in spring-test is not representative of the behavior on spring-web
Other relevant observations
Using the result matcher in MockMvc also doesn't work. The issue here is that the expected MediaType is getting deprecated. Example:
MvcResult result = mockMvc.perform(get("/{foo}/bar", "foo")
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andReturn();
public ResultMatcher contentType(MediaType contentType) {
return result -> {
String actual = result.getResponse().getContentType();
assertNotNull("Content type not set", actual);
assertEquals("Content type", contentType, MediaType.parseMediaType(actual));
};
}
https://github.com/spring-projects/spring-framework/blob/5.3.x/spring-test/src/main/java/org/springframework/test/web/servlet/result/ContentResultMatchers.java#L86
Comment From: bclozel
Closing this issue as the suggestions are the preferred way to assert content types in tests.