测试

时间:2015-08-07 17:40:01

标签: spring spring-mvc jackson

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

我正在使用the method detailed on Spring's blog创建新的ObjectMapper并将其添加到转换器列表中。具体如下:

package com.myproject.config;

import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
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) {
        Jackson2ObjectMapperBuilder builder = jacksonBuilder();
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
    }

    public Jackson2ObjectMapperBuilder jacksonBuilder() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

        return builder;
    }
}

然后我运行以下测试(使用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());
    }
}

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

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

什么时候得到:

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

我有一个实现的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));
    }
}

在IntelliJ中使用我的调试器,我可以看到构建器已创建并添加,但在某个地方,生成的ObjectMapper实际上并未使用。我必须遗漏一些东西,但我设法发现的所有例子似乎都没有提到它是什么!我尝试使用@EnableWebMvc作为Bean消除WebMvcConfigurationSupport并实现MappingJackson2HttpMessageConverter,并将ObjectMapper设置为Bean无效。

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

谢谢!

编辑:正在进行更多挖掘并找到this。在链接中,作者在他/她构建MockMvc之前附加setMessageConverters()并且它适用于作者。做同样的事也对我有用;但是,我不确定一切都能在生产中发挥作用,因为存储库还没有被冲洗掉。当我发现我会提交答案时。

编辑2:请参阅答案。

4 个答案:

答案 0 :(得分:27)

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

  1. 创建一个扩展WebConfig。{/ li>的WebMvcConfigurerAdapter
  2. WebConfig课程中,创建一个返回@Bean的新MappingJackson2HttpMessageConverter。此MappingJackson2HttpMessageConverter已对其应用了所需的更改(在我的情况下,它将Jackson2ObjectMapperBuilder设置为PropertyNamingStrategy并将其传递给CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES。)
  3. 同样在WebConfig班级中,@Override configureMessageConverters()并将(2)中的MappingJackson2HttpMessageConverter添加到邮件转换器列表中。
  4. 在测试文件中,添加@ContextConfiguration(classes = { WebConfig.class })注释以通知您@Bean的测试。
  5. 使用@Autowired注入并访问(2)中定义的@Bean
  6. MockMvc的设置中,使用.setMessageConverters()方法并将注入的MappingJackson2HttpMessageConverter传递给它。该测试现在将使用(2)中的配置。
  7. 测试文件:

    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 MappingJackson2HttpMessageConverter jackson2HttpMessageConverter;
    
        @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.jackson2HttpMessageConverter) // 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());
        }
    }
    

    配置文件:

    package com.myproject.config;
    import com.fasterxml.jackson.databind.PropertyNamingStrategy;
    import org.springframework.context.annotation.*;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    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(jackson2HttpMessageConverter());
        }
    
        @Bean
        public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
            MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
            Jackson2ObjectMapperBuilder builder = this.jacksonBuilder();
            converter.setObjectMapper(builder.build());
    
            return converter;
        }
    
        public Jackson2ObjectMapperBuilder jacksonBuilder() {
            Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); 
            builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
    
            return builder;
        }
    }
    

    将生成的WAR文件部署到XAMPP中的Tomcat 7,表明正在正确使用命名策略。我认为这样做的原因是因为使用独立设置,除非另有说明,否则始终使用一组默认的消息转换器。这可以在StandAloneMockMvcBuilder.java(版本4.1.6,setMessageConverters())中\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;
        }
    

    因此,如果在构建MockMvc期间未明确告知MockMvc对消息转换器的更改,则不会使用更改。

答案 1 :(得分:14)

或者你可以

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

答案 2 :(得分:6)

使用Spring Boot 1.5.1,我可以这样做:

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

    @Autowired
    ObjectMapper objectMapper;
}

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

我的运行时杰克逊配置如下:

@Configuration
public class JacksonConfiguration {

    @Autowired
    Environment environment;

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

            if (JacksonConfiguration.this.environment == null
                    || !JacksonConfiguration.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);
        };
    }
}

答案 3 :(得分:3)

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

@Autowired
private ObjectMapper objectMapper;

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