spring 测试中未使用自定义 ObjectMapper
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31883657/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Customized ObjectMapper not used in test
提问by jsd6547
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.
我使用的是 Spring Framework,版本 4.1.6,带有 Spring Web 服务但没有 Spring Boot。为了学习该框架,我正在编写一个 REST API 并进行测试以确保从点击端点收到的 JSON 响应是正确的。具体来说,我正在尝试调整ObjectMapper
'sPropertyNamingStrategy
以使用“带下划线的小写”命名策略。
I am using the method detailed on Spring's blogto create a new ObjectMapper
and add it to the list of converters. This is as follows:
我正在使用Spring 博客中详述的方法来创建一个新的ObjectMapper
并将其添加到转换器列表中。如下:
package com.myproject.config;
import com.fasterxml.Hymanson.databind.PropertyNamingStrategy;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Hymanson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingHymanson2HttpMessageConverter;
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) {
Hymanson2ObjectMapperBuilder builder = HymansonBuilder();
converters.add(new MappingHymanson2HttpMessageConverter(builder.build()));
}
public Hymanson2ObjectMapperBuilder HymansonBuilder() {
Hymanson2ObjectMapperBuilder builder = new Hymanson2ObjectMapperBuilder();
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:
然后我运行以下测试(使用 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());
}
}
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:
我有一个实现的 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));
}
}
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 MappingHymanson2HttpMessageConverter
as a Bean, and setting ObjectMapper
as a Bean to no avail.
在 IntelliJ 中使用我的调试器,我可以看到构建器已创建并添加,但在某处,结果ObjectMapper
实际上并未使用。我一定遗漏了一些东西,但我设法找到的所有例子似乎都没有提到那是什么!我试过消除@EnableWebMvc
和实现WebMvcConfigurationSupport
,MappingHymanson2HttpMessageConverter
用作 Bean,并设置ObjectMapper
为 Bean 无济于事。
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. 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.
编辑:正在做更多的挖掘并找到了这个。在链接中,作者setMessageConverters()
在他/她构建 MockMvc 之前附加,它对作者有效。做同样的事情对我也有效;但是,我不确定一切是否会在生产中运行,因为存储库尚未清除。当我发现时,我会提交答案。
EDIT 2:See answer.
编辑 2:见答案。
采纳答案by jsd6547
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:
我研究了为什么这会以这种方式工作。重申一下,让我的自定义 ObjectMapper 在我的测试中工作的过程(假设 MockMvc 是作为独立创建的)如下:
- Create a
WebConfig
class that extendsWebMvcConfigurerAdapter
. - In the
WebConfig
class, create a new@Bean
that returns aMappingHymanson2HttpMessageConverter
. ThisMappingHymanson2HttpMessageConverter
has the desired changes applied to it (in my case, it was passing it aHymanson2ObjectMapperBuilder
with thePropertyNamingStrategy
set toCAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
.) - Also in the
WebConfig
class,@Override
configureMessageConverters()
and add theMappingHymanson2HttpMessageConverter
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 injectedMappingHymanson2HttpMessageConverter
. The test will now use the configuration set in (2).
- 创建一个
WebConfig
扩展WebMvcConfigurerAdapter
. - 在
WebConfig
类中,创建一个@Bean
返回MappingHymanson2HttpMessageConverter
. 这MappingHymanson2HttpMessageConverter
已对其应用了所需的更改(在我的情况下,它将 aHymanson2ObjectMapperBuilder
与PropertyNamingStrategy
set 一起传递给CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
.) - 同样在
WebConfig
类中,@Override
configureMessageConverters()
并将MappingHymanson2HttpMessageConverter
from (2)添加到消息转换器列表中。 - 在测试文件中,添加
@ContextConfiguration(classes = { WebConfig.class })
注释以通知测试您的@Bean
. - 使用
@Autowired
注入和访问的@Bean
(2)中所定义。 - 在 的设置中
MockMvc
,使用该.setMessageConverters()
方法并将其传递给注入的MappingHymanson2HttpMessageConverter
. 测试现在将使用 (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 MappingHymanson2HttpMessageConverter Hymanson2HttpMessageConverter;
@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.Hymanson2HttpMessageConverter) // 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.Hymanson.databind.PropertyNamingStrategy;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Hymanson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingHymanson2HttpMessageConverter;
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(Hymanson2HttpMessageConverter());
}
@Bean
public MappingHymanson2HttpMessageConverter Hymanson2HttpMessageConverter() {
MappingHymanson2HttpMessageConverter converter = new MappingHymanson2HttpMessageConverter();
Hymanson2ObjectMapperBuilder builder = this.HymansonBuilder();
converter.setObjectMapper(builder.build());
return converter;
}
public Hymanson2ObjectMapperBuilder HymansonBuilder() {
Hymanson2ObjectMapperBuilder builder = new Hymanson2ObjectMapperBuilder();
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
):
将我生成的 WAR 文件部署到 XAMPP 中的 Tomcat 7 表明正确使用了命名策略。我认为这样做的原因是因为在独立设置中,除非另有说明,否则总是使用一组默认的消息转换器。这可以setMessageConverters()
在 StandAloneMockMvcBuilder.java(版本 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.
因此,如果在构建 MockMvc 期间没有明确告知 MockMvc 对消息转换器的更改,它将不会使用这些更改。
回答by gigi2
or you can
或者你可以
MappingHymanson2HttpMessageConverter mappingHymanson2HttpMessageConverter = new
MappingHymanson2HttpMessageConverter();
mappingHymanson2HttpMessageConverter.setObjectMapper( new ObjectMapper().setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES) );
mockMvc = MockMvcBuilders.standaloneSetup(attributionController).setMessageConverters(
mappingHymanson2HttpMessageConverter ).build();
回答by Magnus Heino
With Spring Boot 1.5.1 I can do:
使用 Spring Boot 1.5.1 我可以做到:
@RunWith(SpringRunner.class)
@AutoConfigureJsonTesters
@JsonTest
public class JsonTest {
@Autowired
ObjectMapper objectMapper;
}
to access the ObjectMapper configured the same way as it is in runtime.
以与运行时相同的方式访问 ObjectMapper。
My runtime Hymanson is configured like this:
我的运行时Hyman逊配置如下:
@Configuration
public class HymansonConfiguration {
@Autowired
Environment environment;
@Bean
public Hymanson2ObjectMapperBuilderCustomizer HymansonCustomizer() {
return builder -> {
builder.locale(new Locale("sv", "SE"));
if (HymansonConfiguration.this.environment == null
|| !HymansonConfiguration.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);
};
}
}
回答by Mr.Q
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:
在 Spring boot 中,当对控制器层 (@WebMvcTest) 进行单元测试时,您可以访问对象映射器,因此您可以在测试用例之前对其进行修改:
@Autowired
private ObjectMapper objectMapper;
@Before
public void init(){
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}