Customized ObjectMapper not used in test
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:
- Create a
WebConfig
class that extendsWebMvcConfigurerAdapter
. - In the
WebConfig
class, create a new@Bean
that returns aMappingJackson2HttpMessageConverter
. ThisMappingJackson2HttpMessageConverter
has the desired changes applied to it (in my case, it was passing it aJackson2ObjectMapperBuilder
with thePropertyNamingStrategy
set toCAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
.) - Also in the
WebConfig
class,@Override
configureMessageConverters()
and add theMappingJackson2HttpMessageConverter
from (2) to the list of message converters. - In the test file, add a
@ContextConfiguration(classes = { WebConfig.class })
annotation to inform the test of your@Bean
. - Use
@Autowired
to inject and access the@Bean
defined in (2). - In the setup of
MockMvc
, use the.setMessageConverters()
method and pass it the injectedMappingJackson2HttpMessageConverter
. The test will now use the configuration set in (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 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());
}
}
And the configuration file:
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;
}
}
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
):
/**
* 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.
or you can
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new
MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setObjectMapper( new ObjectMapper().setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES) );
mockMvc = MockMvcBuilders.standaloneSetup(attributionController).setMessageConverters(
mappingJackson2HttpMessageConverter ).build();