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.