MockMvc 不再使用 Spring Boot 2.2.0.RELEASE 处理 UTF-8 字符

MockMvc no longer handles UTF-8 characters with Spring Boot 2.2.0.RELEASE

升级到 Spring 新发布的 2.2.0.RELEASE 版本后,我的一些测试失败了。 MediaType.APPLICATION_JSON_UTF8 似乎已被弃用,不再作为默认内容类型从未明确指定内容类型的控制器方法中返回。

测试代码如

String content = mockMvc.perform(get("/some-api")
            .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
            .andReturn()
            .getResponse()
            .getContentAsString();

由于内容类型不匹配,突然不再工作,如下所示

java.lang.AssertionError: Content type 
Expected :application/json;charset=UTF-8
Actual   :application/json

将代码更改为 .andExpect(content().contentType(MediaType.APPLICATION_JSON)) 暂时解决了该问题。

但是现在将 content 与预期的序列化对象进行比较时,如果对象中有任何特殊字符,仍然存在不匹配。看来 .getContentAsString() 方法默认不使用 UTF-8 字符编码(不再)。

java.lang.AssertionError: Response content expected:<[{"description":"Er hörte leise Schritte hinter sich."}]> but was:<[{"description":"Er hörte leise Schritte hinter sich."}]>
Expected :[{"description":"Er hörte leise Schritte hinter sich."}]
Actual   :[{"description":"Er hörte leise Schritte hinter sich."}]

如何获得 UTF-8 编码的 content

使用 .getContentAsString(StandardCharsets.UTF_8) 而不是 .getContentAsString() 可以解决问题。

是的。这是 2.2.0 spring-boot 的问题。他们为默认字符集编码设置了弃用。

.getContentAsString(StandardCharsets.UTF_8) - 很好,但在任何响应中都会默认填充 ISO 8859-1。

在我的项目中,我更新了当前创建的转换器:

@Configuration
public class SpringConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.stream()
            .filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
            .findFirst()
            .ifPresent(converter -> ((MappingJackson2HttpMessageConverter) converter).setDefaultCharset(UTF_8));
    }
...

spring5.2.0版本后默认编码字符不再是UTF-8。

要继续使用 UTF-8,您必须在 MockMvc 结果的 ServletResponse 中设置它。 要将默认字符编码设置为 UTF-8,请在您的设置方法中执行如下操作:

@Before
public void setUp() {
   mockMvc = webAppContextSetup(wac).addFilter(((request, response, chain) -> {
                response.setCharacterEncoding("UTF-8");
                chain.doFilter(request, response);
            })).build();
}

然后您可以使用 mockMvc 实例来执行您的请求。

希望对您有所帮助。

根据来自 spring 开发人员的 this 拉取请求,UTF-8 header 不再需要,因此已弃用。如果您在您的应用程序中使用 UTF-8 header,您可能会考虑将其从您的应用程序中删除,而不是尝试修复您的测试。只要确保您使用的是 Content-Type: application/json header 就可以了。

我正在使用 Spring Boot 1.5。15.RELEASE 并且在编写测试时遇到了同样的问题。

对我有帮助的第一个解决方案是像这样添加 .characterEncoding("UTF-8")) :

String content = mockMvc.perform(get("/some-api")
            .contentType(MediaType.APPLICATION_JSON)
            .characterEncoding("UTF-8"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
            .andReturn()
            .getResponse()
            .getContentAsString();

我在测试中使用了 StandaloneMockMvcBuilder class,所以第二个帮助我的解决方案是创建一个过滤器,例如:

private static class Utf8Filter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        filterChain.doFilter(request, response);
    }
}

然后将其添加到我的测试 class 中的 standaloneSetup 方法中,如下所示:

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
    final SomeResource someResource = new SomeResource(someService);
    this.restLankMockMvc = MockMvcBuilders.standaloneSetup(someResource)
        .setCustomArgumentResolvers(pageableArgumentResolver)
        .setControllerAdvice(exceptionTranslator)
        .setConversionService(createFormattingConversionService())
        .setMessageConverters(jacksonMessageConverter)
        .addFilter(new Utf8Filter())
        .build();
}

对 MockMvc 的附加设置,.accept(MediaType.APPLICATION_JSON_UTF8_VALUE):

    String content = mockMvc.perform(get("/some-api")
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON_UTF8_VALUE))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        .andReturn()
        .getResponse()
        .getContentAsString();

这个问题不是 Spring 引导,而是 MockMvc 特定问题,我猜。 因此,必须仅将解决方法应用于 MockMvc。 (JSON must be encoded using UTF-8.)

相关问题:Improper UTF-8 handling in MockMvc for JSON response · Issue #23622 · spring-projects/spring-framework

要恢复原始行为 (Content-Type=application/json;charset=UTF-8) 并让您的测试按原样通过,您可以执行以下操作:

@Configuration
public class MyWebConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_JSON_UTF8);
    }
...

但是,由于 APPLICATION_JSON_UTF8 已被弃用并且 Spring 的意图是不包含字符集,因此最好继续修改您的测试。 black4bird 的解决方案对我有用。

根据 black4bird 的回答,您可以通过在测试 Spring 上下文中放置以下 MockMvcBuilderCustomizer 实现来覆盖所有测试的字符编码:

@Component
class MockMvcCharacterEncodingCustomizer implements MockMvcBuilderCustomizer {
    @Override
    public void customize(ConfigurableMockMvcBuilder<?> builder) {
        builder.alwaysDo(result -> result.response.characterEncoding = "UTF-8");
    }
}

如果您不想显式设置 MockMvc,而只使用 @AutoconfigureMockMvc

,这可能会有所帮助