mvc jsonview example spring spring-mvc jackson

spring - jsonview - ObjectMapper personalizado no utilizado en la prueba



spring mvc jackson example (3)

Busqué entender por qué esto funciona de la manera en que lo hizo. Para reiterar, el proceso para que mi ObjectMapper personalizado funcione en mi prueba (suponiendo que MockMvc se está creando como independiente) es el siguiente:

  1. Cree una clase WebConfig que amplíe WebMvcConfigurerAdapter .
  2. En la clase WebConfig , cree un nuevo @Bean que devuelva un MappingJackson2HttpMessageConverter . Este MappingJackson2HttpMessageConverter tiene los cambios deseados aplicados (en mi caso, le estaba pasando un Jackson2ObjectMapperBuilder con PropertyNamingStrategy establecido en CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES ).
  3. También en la clase WebConfig , @Override configureMessageConverters() y agregue MappingJackson2HttpMessageConverter de (2) a la lista de convertidores de mensajes.
  4. En el archivo de prueba, agregue una @ContextConfiguration(classes = { WebConfig.class }) para informar la prueba de su @Bean .
  5. Utilice @Autowired para inyectar y acceder al @Bean definido en (2).
  6. En la configuración de MockMvc , use el método .setMessageConverters() y MappingJackson2HttpMessageConverter el MappingJackson2HttpMessageConverter inyectado. La prueba ahora utilizará la configuración establecida en (2).

El archivo de prueba:

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

Y el archivo de configuración:

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

Al implementar mi archivo WAR generado en Tomcat 7 en XAMPP, se muestra que la estrategia de asignación de nombres se está utilizando correctamente. La razón por la que creo que esto funciona de la manera en que lo hace es porque con una configuración independiente, siempre se usa un conjunto predeterminado de convertidores de mensajes, a menos que se especifique lo contrario. Esto se puede ver en el comentario de la función setMessageConverters() dentro de StandAloneMockMvcBuilder.java (versión 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; }

Por lo tanto, si no se informa explícitamente a MockMvc sobre los cambios realizados en los conversores de mensajes durante la creación de MockMvc, no se utilizarán los cambios.

Estoy utilizando Spring Framework, versión 4.1.6, con servicios web de Spring y sin Spring Boot. Para aprender el marco, estoy escribiendo una API REST y estoy probando para asegurarme de que la respuesta JSON recibida al llegar a un punto final sea correcta. Específicamente, estoy tratando de ajustar PropertyNamingStrategy ObjectMapper para usar la estrategia de nomenclatura "en minúsculas con guiones bajos".

Estoy usando el método detallado en el blog de Spring para crear un nuevo ObjectMapper y agregarlo a la lista de convertidores. Esto es como sigue:

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

Luego ejecuto la siguiente prueba (utilizando JUnit, MockMvc y Mockito) para verificar mis cambios:

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

Sin embargo, esto devuelve una respuesta de:

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

Cuando debería obtener:

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

Tengo un WebApplicationInitializer implementado que busca el paquete:

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

Al usar mi depurador dentro de IntelliJ, puedo ver que el generador se crea y agrega, pero en algún lugar de la línea no se usa el ObjectMapper resultante. Debo estar perdiendo algo, ¡pero todos los ejemplos que logré encontrar no parecen mencionar lo que es eso! He intentado eliminar @EnableWebMvc e implementar WebMvcConfigurationSupport , utilizando MappingJackson2HttpMessageConverter como Bean y configurando ObjectMapper como Bean en vano.

Cualquier ayuda sería muy apreciada! Por favor, hágamelo saber si se requieren otros archivos.

¡Gracias!

EDIT: Estaba haciendo un poco más de excavación y encontré this . En el enlace, el autor agrega setMessageConverters() antes de que él / ella construya MockMvc y funcione para el autor. Hacer lo mismo me funcionó a mí también; sin embargo, no estoy seguro de si todo funcionará en producción ya que los repositorios aún no se han vaciado. Cuando lo descubra le enviaré una respuesta.

EDIT 2: ver respuesta.


Con Spring Boot 1.5.1 puedo hacer:

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

para acceder al ObjectMapper configurado de la misma manera que en el tiempo de ejecución.

Mi runtime jackson está configurado así:

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


o tu puedes

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