복붙노트

[SPRING] 테스트에 사용되지 않는 사용자 정의 ObjectMapper

SPRING

테스트에 사용되지 않는 사용자 정의 ObjectMapper

스프링 웹 서비스와 스프링 부트없이 스프링 프레임 워크, 버전 4.1.6을 사용하고 있습니다. 프레임 워크를 배우려면 REST API를 작성하고 JSON 응답이 엔드 포인트를 치는 것으로부터 수신되었는지 테스트해야합니다. 특히 ObjectMapper의 PropertyNamingStrategy가 "밑줄이있는 밑줄"명명 전략을 사용하도록 조정하려고합니다.

Spring의 블로그에 자세하게 설명 된 메소드를 사용하여 새로운 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를 제거하고 WebMvcConfigurationSupport를 구현하고 MappingJackson2HttpMessageConverter를 Bean으로 사용하고 ObjectMapper를 Bean으로 설정하여 사용하지 않으려 고 노력했습니다.

어떤 도움이라도 대단히 감사하겠습니다! 다른 파일이 필요한지 알려주십시오.

감사!

편집 : 좀 더 파고하고 이것을 발견했다. 이 링크에서 저자는 MockMvc를 만들기 전에 setMessageConverters ()를 추가하고 작성자를 위해 작동합니다. 같은 일을하는 것이 나를 위해서도 효과가있었습니다. 그러나 저장소가 아직 플러시되지 않았기 때문에 모든 것이 프로덕션 환경에서 작동하는지 확신 할 수 없습니다. 알아 내면 대답을 제출할 것입니다.

편집 2 : 답변을 참조하십시오.

해결법

  1. ==============================

    1.나는 왜 이것이 어떻게 작동 하는지를 이해하게되었습니다. 다시 말하면 내 맞춤형 ObjectMapper가 내 테스트에서 작동하도록하는 프로세스 (MockMvc가 독립형으로 작성되었다고 가정)는 다음과 같습니다.

    나는 왜 이것이 어떻게 작동 하는지를 이해하게되었습니다. 다시 말하면 내 맞춤형 ObjectMapper가 내 테스트에서 작동하도록하는 프로세스 (MockMvc가 독립형으로 작성되었다고 가정)는 다음과 같습니다.

    테스트 파일 :

    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, \ org \ springframework \ test \ web \ servlet \ setup \ StandaloneMockMvcBuilder.java) 내의 setMessageConverters () 함수에 대한 주석에서 확인할 수 있습니다.

       /**
         * 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를 빌드하는 동안 메시지 변환기에 대한 변경 사항에 대해 MockMvc에 명시 적으로 알려주지 않으면 변경 사항을 사용하지 않습니다.

  2. ==============================

    2.또는 당신은 할 수있다.

    또는 당신은 할 수있다.

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

    3.스프링 부트 1.5.1에서는 다음과 같은 작업을 수행 할 수 있습니다.

    스프링 부트 1.5.1에서는 다음과 같은 작업을 수행 할 수 있습니다.

    @RunWith(SpringRunner.class)
    @AutoConfigureJsonTesters
    @JsonTest
    public class JsonTest {
    
        @Autowired
        ObjectMapper objectMapper;
    }
    

    런타임시와 동일한 방식으로 구성된 ObjectMapper에 액세스합니다.

    내 런타임 jackson은 다음과 같이 구성됩니다.

    @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);
            };
        }
    }
    
  4. from https://stackoverflow.com/questions/31883657/customized-objectmapper-not-used-in-test by cc-by-sa and MIT license