【问题标题】:MockMvc no longer handles UTF-8 characters with Spring Boot 2.2.0.RELEASEMockMvc 不再使用 Spring Boot 2.2.0.RELEASE 处理 UTF-8 字符
【发布时间】:2020-02-19 20:24:42
【问题描述】:

在我升级到新发布的2.2.0.RELEASE 版本的 Spring Boot 后,我​​的一些测试失败了。 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

【问题讨论】:

    标签: spring-boot spring-mvc jackson spring-test


    【解决方案1】:

    是的。这是 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));
        }
    ...
    

    【讨论】:

    • 这是这里推荐的最简单的解决方案!
    • 这有点误导,因为它只会导致内容类型标头为application/json;charset=UTF-8,这违背了 Spring 开始弃用字符集部分的目的。那么修复测试不是更好吗?但是,如果您要使用已弃用的值,我认为正确的方法是使用 ContentNegotiationConfigurer
    【解决方案2】:

    使用.getContentAsString(StandardCharsets.UTF_8) 代替.getContentAsString() 可以解决问题。

    【讨论】:

    【解决方案3】:

    从spring 5.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 实例来执行您的请求。

    希望对您有所帮助。

    【讨论】:

    • 使用这个解决方案,我必须在每个测试类中配置 mockMvc。对于许多测试课程来说,这可能非常乏味!
    • 你可以创建一个抽象类并在那里做,然后在你的测试类中扩展它。
    【解决方案4】:

    我使用的是 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,所以帮助我的第二个解决方案是创建一个过滤器,例如:

    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);
        }
    }
    

    然后将其添加到我的测试类中的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();
    }
    

    【讨论】:

      【解决方案5】:

      要恢复原始行为 (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 的解决方案对我有用。

      【讨论】:

      • 这应该是公认的答案。不创建任何额外的过滤器,也不修改现有的转换器。而且代码是可读的
      【解决方案6】:

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

      【讨论】:

      • 我想你不明白这个问题。我建议您阅读整个问题,然后重新评估,如果您的答案有任何价值。我的应用程序运行良好,问题与测试有关。
      • 我再次阅读了整个问题并重新评估了我的答案,答案仍然相同。在您的问题中,您没有解释 为什么 标题已被弃用,我用我的帖子丰富了您的问题。我建议您阅读我链接到的 PR,以便您了解为什么不推荐使用标头。如果您了解原因,您可能需要考虑更改您的测试,因为您的测试正在测试 Spring 2.1.X 中的默认行为,但它没有正确测试 Spring 2.2.X 中的行为。 Spring 行为发生了变化,如果您接受新的 Spring 行为,您的测试应该相应地发生变化。
      • 你在这里不是很一致。在您的回答中,您说“[...] 而不是尝试修复您的测试”。在您的评论中,您说“[...] 如果您接受新的 Spring 行为,您的测试应该相应地改变。”
      • 每个程序员都会时不时地面临弃用的值。当某些东西被弃用时,您可以以某种方式修复它,而无需研究为什么它首先被弃用。这种方法似乎是您处理此问题的方式。现在我建议你进一步研究为什么它被弃用了。如果你明白这一点,你就可以更好地决定下一步该做什么。在您的问题中,没有任何关于 为什么 您只告诉我们您的测试失败是由于不推荐使用的值,这是一个糟糕的研究。我用你没有做的一些研究丰富了这个问题,并且对 Q 投了赞成票。
      【解决方案7】:

      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 Boot,而是 MockMvc 特有的问题。 因此,解决方法必须仅应用于 MockMvc。 (JSON must be encoded using UTF-8.)

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

      【解决方案8】:

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

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

      如果您不想显式设置 MockMvc,而只需使用 @AutoconfigureMockMvc,这可能会有所帮助。

      【讨论】:

        猜你喜欢
        • 2021-08-13
        • 1970-01-01
        • 1970-01-01
        • 2019-08-01
        • 2021-12-22
        • 2020-08-31
        • 1970-01-01
        • 2015-12-28
        • 1970-01-01
        相关资源
        最近更新 更多