spring 测试中未使用自定义 ObjectMapper

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31883657/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 00:42:33  来源:igfitidea点击:

Customized ObjectMapper not used in test

springspring-mvcHymanson

提问by jsd6547

I am using the Spring Framework, version 4.1.6, with Spring web services and without Spring Boot. To learn the framework, I am writing a REST API and am testing to make sure that the JSON response received from hitting an endpoint is correct. Specifically, I am trying to adjust the ObjectMapper's PropertyNamingStrategyto use the "lower case with underscores" naming strategy.

我使用的是 Spring Framework,版本 4.1.6,带有 Spring Web 服务但没有 Spring Boot。为了学习该框架,我正在编写一个 REST API 并进行测试以确保从点击端点收到的 JSON 响应是正确的。具体来说,我正在尝试调整ObjectMapper'sPropertyNamingStrategy以使用“带下划线的小写”命名策略。

I am using the method detailed on Spring's blogto create a new ObjectMapperand add it to the list of converters. This is as follows:

我正在使用Spring 博客中详述的方法来创建一个新的ObjectMapper并将其添加到转换器列表中。如下:

package com.myproject.config;

import com.fasterxml.Hymanson.databind.PropertyNamingStrategy;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Hymanson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingHymanson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Hymanson2ObjectMapperBuilder builder = HymansonBuilder();
        converters.add(new MappingHymanson2HttpMessageConverter(builder.build()));
    }

    public Hymanson2ObjectMapperBuilder HymansonBuilder() {
        Hymanson2ObjectMapperBuilder builder = new Hymanson2ObjectMapperBuilder();
        builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

        return builder;
    }
}

Then I run the following test (using JUnit, MockMvc, and Mockito) to verify my changes:

然后我运行以下测试(使用 JUnit、MockMvc 和 Mockito)来验证我的更改:

package com.myproject.controller;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.AnnotationConfigWebContextLoader;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

// Along with other application imports...

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {WebConfig.class}, loader = AnnotationConfigWebContextLoader.class)
public class MyControllerTest {

    @Mock
    private MyManager myManager;

    @InjectMocks
    private MyController myController;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(this.myController).build();
    }


    @Test
    public void testMyControllerWithNameParam() throws Exception {
        MyEntity expected = new MyEntity();
        String name = "expected";
        String title = "expected title";

        // Set up MyEntity with data.
        expected.setId(1); // Random ID.
        expected.setEntityName(name);
        expected.setEntityTitle(title)

        // When the MyManager instance is asked for the MyEntity with name parameter,
        // return expected.
        when(this.myManager.read(name)).thenReturn(expected);

        // Assert the proper results.
        MvcResult result = mockMvc.perform(
                get("/v1/endpoint")
                    .param("name", name))
                .andExpect(status().isOk())
                .andExpect((content().contentType("application/json;charset=UTF-8")))
                .andExpect(jsonPath("$.entity_name", is(name))))
                .andExpect(jsonPath("$.entity_title", is(title)))
                .andReturn();

        System.out.println(result.getResponse().getContentAsString());
    }
}

However, this returns a response of:

但是,这会返回以下响应:

{"id": 1, "entityName": "expected", "entityTitle": "expected title"}

When I should get:

当我应该得到:

{"id": 1, "entity_name": "expected", "entity_title": "expected title"}

I have an implemented WebApplicationInitializer that scans for the package:

我有一个实现的 WebApplicationInitializer 扫描包:

package com.myproject.config;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class WebAppInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.scan("com.myproject.config");
        ctx.setServletContext(servletContext);

        ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));
        servlet.setLoadOnStartup(1);
        servlet.addMapping("/");

        servletContext.addListener(new ContextLoaderListener(ctx));
    }
}

Using my debugger within IntelliJ, I can see that the builder is created and added, but somewhere down the line the resulting ObjectMapperis not actually used. I must be missing something, but all the examples I've managed to find don't seem to mention what that is! I've tried eliminating @EnableWebMvcand implementing WebMvcConfigurationSupport, using MappingHymanson2HttpMessageConverteras a Bean, and setting ObjectMapperas a Bean to no avail.

在 IntelliJ 中使用我的调试器,我可以看到构建器已创建并添加,但在某处,结果ObjectMapper实际上并未使用。我一定遗漏了一些东西,但我设法找到的所有例子似乎都没有提到那是什么!我试过消除@EnableWebMvc和实现WebMvcConfigurationSupportMappingHymanson2HttpMessageConverter用作 Bean,并设置ObjectMapper为 Bean 无济于事。

Any help would be greatly appreciated! Please let me know if any other files are required.

任何帮助将不胜感激!如果需要任何其他文件,请告诉我。

Thanks!

谢谢!

EDIT:Was doing some more digging and found this. In the link, the author appends setMessageConverters()before he/she builds MockMvc and it works for the author. Doing the same worked for me as well; however, I'm not sure if everything will work in production as the repositories aren't flushed out yet. When I find out I will submit an answer.

编辑:正在做更多的挖掘并找到了这个。在链接中,作者setMessageConverters()在他/她构建 MockMvc 之前附加,它对作者有效。做同样的事情对我也有效;但是,我不确定一切是否会在生产中运行,因为存储库尚未清除。当我发现时,我会提交答案。

EDIT 2:See answer.

编辑 2:见答案。

采纳答案by jsd6547

I looked into understanding why this works the way that it did. To reiterate, the process of getting my customized ObjectMapper to work in my test (assuming MockMvc is being created as a standalone) is as follows:

我研究了为什么这会以这种方式工作。重申一下,让我的自定义 ObjectMapper 在我的测试中工作的过程(假设 MockMvc 是作为独立创建的)如下:

  1. Create a WebConfigclass that extends WebMvcConfigurerAdapter.
  2. In the WebConfigclass, create a new @Beanthat returns a MappingHymanson2HttpMessageConverter. This MappingHymanson2HttpMessageConverterhas the desired changes applied to it (in my case, it was passing it a Hymanson2ObjectMapperBuilderwith the PropertyNamingStrategyset to CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES.)
  3. Also in the WebConfigclass, @OverrideconfigureMessageConverters()and add the MappingHymanson2HttpMessageConverterfrom (2) to the list of message converters.
  4. In the test file, add a @ContextConfiguration(classes = { WebConfig.class })annotation to inform the test of your @Bean.
  5. Use @Autowiredto inject and access the @Beandefined in (2).
  6. In the setup of MockMvc, use the .setMessageConverters()method and pass it the injected MappingHymanson2HttpMessageConverter. The test will now use the configuration set in (2).
  1. 创建一个WebConfig扩展WebMvcConfigurerAdapter.
  2. WebConfig类中,创建一个@Bean返回MappingHymanson2HttpMessageConverter. 这MappingHymanson2HttpMessageConverter已对其应用了所需的更改(在我的情况下,它将 aHymanson2ObjectMapperBuilderPropertyNamingStrategyset 一起传递给CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES.)
  3. 同样在WebConfig类中,@OverrideconfigureMessageConverters()并将MappingHymanson2HttpMessageConverterfrom (2)添加到消息转换器列表中。
  4. 在测试文件中,添加@ContextConfiguration(classes = { WebConfig.class })注释以通知测试您的@Bean.
  5. 使用@Autowired注入和访问的@Bean(2)中所定义。
  6. 在 的设置中MockMvc,使用该.setMessageConverters()方法并将其传递给注入的MappingHymanson2HttpMessageConverter. 测试现在将使用 (2) 中设置的配置。

The test file:

测试文件:

package com.myproject.controller;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.AnnotationConfigWebContextLoader;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

// Along with other application imports...

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {WebConfig.class})
public class MyControllerTest {

    /**
     * Note that the converter needs to be autowired into the test in order for
     * MockMvc to recognize it in the setup() method.
     */
    @Autowired
    private MappingHymanson2HttpMessageConverter Hymanson2HttpMessageConverter;

    @Mock
    private MyManager myManager;

    @InjectMocks
    private MyController myController;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders
            .standaloneSetup(this.myController)
            .setMessageConverters(this.Hymanson2HttpMessageConverter) // Important!
            .build();
    }


    @Test
    public void testMyControllerWithNameParam() throws Exception {
        MyEntity expected = new MyEntity();
        String name = "expected";
        String title = "expected title";

        // Set up MyEntity with data.
        expected.setId(1); // Random ID.
        expected.setEntityName(name);
        expected.setEntityTitle(title)

        // When the MyManager instance is asked for the MyEntity with name parameter,
        // return expected.
        when(this.myManager.read(name)).thenReturn(expected);

        // Assert the proper results.
        MvcResult result = mockMvc.perform(
                get("/v1/endpoint")
                    .param("name", name))
                .andExpect(status().isOk())
                .andExpect((content().contentType("application/json;charset=UTF-8")))
                .andExpect(jsonPath("$.entity_name", is(name))))
                .andExpect(jsonPath("$.entity_title", is(title)))
                .andReturn();

        System.out.println(result.getResponse().getContentAsString());
    }
}

And the configuration file:

和配置文件:

package com.myproject.config;
import com.fasterxml.Hymanson.databind.PropertyNamingStrategy;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Hymanson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingHymanson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(Hymanson2HttpMessageConverter());
    }

    @Bean
    public MappingHymanson2HttpMessageConverter Hymanson2HttpMessageConverter() {
        MappingHymanson2HttpMessageConverter converter = new MappingHymanson2HttpMessageConverter();
        Hymanson2ObjectMapperBuilder builder = this.HymansonBuilder();
        converter.setObjectMapper(builder.build());

        return converter;
    }

    public Hymanson2ObjectMapperBuilder HymansonBuilder() {
        Hymanson2ObjectMapperBuilder builder = new Hymanson2ObjectMapperBuilder(); 
        builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

        return builder;
    }
}

Deploying my generated WAR file to Tomcat 7 in XAMPP shows that the naming strategy is being used correctly. The reason I believe that this works the way that it does is because with a standalone setup, a default set of message converters is always used unless otherwise specified. This can be seen in the comment for the setMessageConverters()function within StandAloneMockMvcBuilder.java (version 4.1.6, \org\springframework\test\web\servlet\setup\StandaloneMockMvcBuilder.java):

将我生成的 WAR 文件部署到 XAMPP 中的 Tomcat 7 表明正确使用了命名策略。我认为这样做的原因是因为在独立设置中,除非另有说明,否则总是使用一组默认的消息转换器。这可以setMessageConverters()在 StandAloneMockMvcBuilder.java(版本 4.1.6,\org\springframework\test\web\servlet\setup\StandaloneMockMvcBuilder.java)中的函数注释中看到:

   /**
     * Set the message converters to use in argument resolvers and in return value
     * handlers, which support reading and/or writing to the body of the request
     * and response. If no message converters are added to the list, a default
     * list of converters is added instead.
     */
    public StandaloneMockMvcBuilder setMessageConverters(HttpMessageConverter<?>...messageConverters) {
        this.messageConverters = Arrays.asList(messageConverters);
        return this;
    }

Therefore, if MockMvc is not explicitly told about one's changes to the message converters during the building of the MockMvc, it will not use the changes.

因此,如果在构建 MockMvc 期间没有明确告知 MockMvc 对消息转换器的更改,它将不会使用这些更改。

回答by gigi2

or you can

或者你可以

MappingHymanson2HttpMessageConverter mappingHymanson2HttpMessageConverter = new
            MappingHymanson2HttpMessageConverter();
    mappingHymanson2HttpMessageConverter.setObjectMapper( new ObjectMapper().setPropertyNamingStrategy(
            PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES) );
    mockMvc = MockMvcBuilders.standaloneSetup(attributionController).setMessageConverters(
            mappingHymanson2HttpMessageConverter ).build();

回答by Magnus Heino

With Spring Boot 1.5.1 I can do:

使用 Spring Boot 1.5.1 我可以做到:

@RunWith(SpringRunner.class)
@AutoConfigureJsonTesters
@JsonTest
public class JsonTest {

    @Autowired
    ObjectMapper objectMapper;
}

to access the ObjectMapper configured the same way as it is in runtime.

以与运行时相同的方式访问 ObjectMapper。

My runtime Hymanson is configured like this:

我的运行时Hyman逊配置如下:

@Configuration
public class HymansonConfiguration {

    @Autowired
    Environment environment;

    @Bean
    public Hymanson2ObjectMapperBuilderCustomizer HymansonCustomizer() {
        return builder -> {
            builder.locale(new Locale("sv", "SE"));

            if (HymansonConfiguration.this.environment == null
                    || !HymansonConfiguration.this.environment.acceptsProfiles("docker")) {
                builder.indentOutput(true);
            }

            final Jdk8Module jdk8Module = new Jdk8Module();

            final ProblemModule problemModule = new ProblemModule();

            final JavaTimeModule javaTimeModule = new JavaTimeModule();

            final Module[] modules = new Module[] { jdk8Module, problemModule,
                javaTimeModule };
            builder.modulesToInstall(modules);
        };
    }
}

回答by Mr.Q

In Spring boot, when unit testing the controller layer (@WebMvcTest), you have access to the object mapper so you can modify it before the test cases:

在 Spring boot 中,当对控制器层 (@WebMvcTest) 进行单元测试时,您可以访问对象映射器,因此您可以在测试用例之前对其进行修改:

@Autowired
private ObjectMapper objectMapper;

@Before
public void init(){
    objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}