Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 843 Vote(s) - 3.54 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Customized ObjectMapper not used in test

#1
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 `PropertyNamingStrategy` to use the "lower case with underscores" naming strategy.

I am using [the method detailed on Spring's blog][1] to create a new `ObjectMapper` and add it to the list of converters. This is as follows:

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

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

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:

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 `ObjectMapper` is 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 `@EnableWebMvc` and implementing `WebMvcConfigurationSupport`, using `MappingJackson2HttpMessageConverter` as a Bean, and setting `ObjectMapper` as a Bean to no avail.

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][2]. 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.

**EDIT 2:** See answer.

[1]:

[To see links please register here]

[2]:

[To see links please register here]

Reply

#2
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:

1. Create a `WebConfig` class that extends `WebMvcConfigurerAdapter`.
2. In the `WebConfig` class, create a new `@Bean` that returns a `MappingJackson2HttpMessageConverter`. This `MappingJackson2HttpMessageConverter` has the desired changes applied to it (in my case, it was passing it a `Jackson2ObjectMapperBuilder` with the `PropertyNamingStrategy` set to `CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES`.)
3. Also in the `WebConfig` class, `@Override` `configureMessageConverters()` and add the `MappingJackson2HttpMessageConverter` from (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 `@Autowired` to inject and access the `@Bean` defined in (2).
6. In the setup of `MockMvc`, use the `.setMessageConverters()` method and pass it the injected `MappingJackson2HttpMessageConverter`. 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.
Reply

#3
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();
Reply

#4
With Spring Boot 1.5.1 I can do:

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

@Autowired
ObjectMapper objectMapper;
}

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

My runtime jackson is configured like this:


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

#5
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:


@Autowired
private ObjectMapper objectMapper;

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



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through