Java 集成测试将整个对象发布到Spring MVC控制器
在集成测试SpringMVCWeb应用程序时,有没有一种方法可以在模拟请求上传递整个表单对象?我所能找到的就是将每个字段分别作为参数传递,如下所示:Java 集成测试将整个对象发布到Spring MVC控制器,java,spring,testing,spring-mvc,integration-testing,Java,Spring,Testing,Spring Mvc,Integration Testing,在集成测试SpringMVCWeb应用程序时,有没有一种方法可以在模拟请求上传递整个表单对象?我所能找到的就是将每个字段分别作为参数传递,如下所示: mockMvc.perform(post("/somehwere/new").param("items[0].value","value")); NewObject { List<Item> selection; } mockMvc.perform(post("/somehwere/new").requestAttr("ne
mockMvc.perform(post("/somehwere/new").param("items[0].value","value"));
NewObject {
List<Item> selection;
}
mockMvc.perform(post("/somehwere/new").requestAttr("newObject", newObject)
@Controller
@RequestMapping(value = "/somewhere/new")
public class SomewhereController {
@RequestMapping(method = RequestMethod.POST)
public String post(
@ModelAttribute("newObject") NewObject newObject) {
// ...
}
// static import (optional)
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
// in your test method, populate your model attribute object (yes, works with nested properties)
BlogSetup bgs = new BlogSetup();
bgs.getBlog().setBlogTitle("Test Blog");
bgs.getUser().setEmail("admin.localhost@example.com");
bgs.getUser().setFirstName("Administrator");
bgs.getUser().setLastName("Localhost");
bgs.getUser().setPassword("password");
// finally put it together
mockMvc.perform(
postForm("/blogs/create", bgs, "blog.blogTitle", "user.email",
"user.firstName", "user.lastName", "user.password"))
.andExpect(status().isOk())
这对于小型表单来说是很好的。但是如果我发布的对象变大了怎么办?如果我可以发布一个完整的对象,它也会使测试代码看起来更好
具体来说,我想通过复选框测试多个项目的选择,然后发布它们。当然,我可以只测试发布一个项目,但我想知道
我们正在使用Spring3.2.2,包括SpringTestMVC
我的表单模型如下所示:
mockMvc.perform(post("/somehwere/new").param("items[0].value","value"));
NewObject {
List<Item> selection;
}
mockMvc.perform(post("/somehwere/new").requestAttr("newObject", newObject)
@Controller
@RequestMapping(value = "/somewhere/new")
public class SomewhereController {
@RequestMapping(method = RequestMethod.POST)
public String post(
@ModelAttribute("newObject") NewObject newObject) {
// ...
}
// static import (optional)
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
// in your test method, populate your model attribute object (yes, works with nested properties)
BlogSetup bgs = new BlogSetup();
bgs.getBlog().setBlogTitle("Test Blog");
bgs.getUser().setEmail("admin.localhost@example.com");
bgs.getUser().setFirstName("Administrator");
bgs.getUser().setLastName("Localhost");
bgs.getUser().setPassword("password");
// finally put it together
mockMvc.perform(
postForm("/blogs/create", bgs, "blog.blogTitle", "user.email",
"user.firstName", "user.lastName", "user.password"))
.andExpect(status().isOk())
对于这样的控制器:
mockMvc.perform(post("/somehwere/new").param("items[0].value","value"));
NewObject {
List<Item> selection;
}
mockMvc.perform(post("/somehwere/new").requestAttr("newObject", newObject)
@Controller
@RequestMapping(value = "/somewhere/new")
public class SomewhereController {
@RequestMapping(method = RequestMethod.POST)
public String post(
@ModelAttribute("newObject") NewObject newObject) {
// ...
}
// static import (optional)
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
// in your test method, populate your model attribute object (yes, works with nested properties)
BlogSetup bgs = new BlogSetup();
bgs.getBlog().setBlogTitle("Test Blog");
bgs.getUser().setEmail("admin.localhost@example.com");
bgs.getUser().setFirstName("Administrator");
bgs.getUser().setLastName("Localhost");
bgs.getUser().setPassword("password");
// finally put it together
mockMvc.perform(
postForm("/blogs/create", bgs, "blog.blogTitle", "user.email",
"user.firstName", "user.lastName", "user.password"))
.andExpect(status().isOk())
但是对象将是空的(是的,我以前在测试中填充过)
我找到的唯一有效解决方案是使用@SessionAttribute,如下所示:
但是我不喜欢在我需要的每个控制器的末尾调用complete。在所有表单数据都不必在会话中之后,我只需要在一个请求中使用它
所以我现在唯一能想到的就是编写一个Util类,它使用MockHttpServletRequestBuilder将所有对象字段附加为.param,使用反射或单独为每个测试用例添加
我不知道,感觉不直观
关于如何使我的爱好更容易些,有什么想法/想法吗?(除了直接呼叫控制器)
谢谢 与
MockMvc
集成测试的主要目的之一是验证模型对象是否正确填充了表单数据
为了做到这一点,您必须像从实际表单传递表单数据一样传递表单数据(使用
.param()
)。如果您使用一些从NewObject
到from data的自动转换,您的测试将不会涵盖特定类别的可能问题(对NewObject
的修改与实际形式不兼容)。我不久前遇到了同样的问题,并在来自的帮助下使用反射解决了它
首先用对象上的所有字段填充贴图。然后将这些映射项作为参数添加到MockHttpServletRequestBuilder
通过这种方式,您可以使用任何对象,并将其作为请求参数传递。我相信还有其他解决方案,但这一方案对我们有效:
@Test
public void testFormEdit() throws Exception {
getMockMvc()
.perform(
addFormParameters(post(servletPath + tableRootUrl + "/" + POST_FORM_EDIT_URL).servletPath(servletPath)
.param("entityID", entityId), validEntity)).andDo(print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(content().string(equalTo(entityId)));
}
private MockHttpServletRequestBuilder addFormParameters(MockHttpServletRequestBuilder builder, Object object)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
SimpleDateFormat dateFormat = new SimpleDateFormat(applicationSettings.getApplicationDateFormat());
Map<String, ?> propertyValues = getPropertyValues(object, dateFormat);
for (Entry<String, ?> entry : propertyValues.entrySet()) {
builder.param(entry.getKey(),
Util.prepareDisplayValue(entry.getValue(), applicationSettings.getApplicationDateFormat()));
}
return builder;
}
private Map<String, ?> getPropertyValues(Object object, DateFormat dateFormat) {
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(dateFormat);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.registerModule(new JodaModule());
TypeReference<HashMap<String, ?>> typeRef = new TypeReference<HashMap<String, ?>>() {};
Map<String, ?> returnValues = mapper.convertValue(object, typeRef);
return returnValues;
}
@测试
public void testFormEdit()引发异常{
getMockMvc()
.表演(
addFormParameters(post(servletPath+tableRootUrl+“/”+post\u FORM\u EDIT\u URL)。servletPath(servletPath)
.param(“entityID”,entityID),validEntity)).andDo(print()).andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(content().string(equalTo(entityId)));
}
私有MockHttpServletRequestBuilder addFormParameters(MockHttpServletRequestBuilder,对象对象)
抛出IllegalAccessException、InvocationTargetException、NoSuchMethodException{
SimpleDataFormat dateFormat=新的SimpleDataFormat(applicationSettings.GetApplicationDataFormat());
Map propertyValues=getPropertyValues(对象,日期格式);
for(条目:propertyValues.entrySet()){
builder.param(entry.getKey(),
Util.prepareDisplayValue(entry.getValue(),applicationSettings.getApplicationDateFormat());
}
返回生成器;
}
私有映射getPropertyValues(对象对象,日期格式DateFormat){
ObjectMapper mapper=新的ObjectMapper();
setDateFormat(日期格式);
setSerializationInclusion(JsonInclude.Include.NON_NULL);
registerModule(新的JodaModule());
TypeReference typeRef=新的TypeReference(){};
Map returnValues=mapper.convertValue(对象,typeRef);
返回值;
}
使用反射但不编组的另一种解决方法:
我有一个抽象的助手类:
public abstract class MvcIntegrationTestUtils {
public static MockHttpServletRequestBuilder postForm(String url,
Object modelAttribute, String... propertyPaths) {
try {
MockHttpServletRequestBuilder form = post(url).characterEncoding(
"UTF-8").contentType(MediaType.APPLICATION_FORM_URLENCODED);
for (String path : propertyPaths) {
form.param(path, BeanUtils.getProperty(modelAttribute, path));
}
return form;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
您可以这样使用它:
mockMvc.perform(post("/somehwere/new").param("items[0].value","value"));
NewObject {
List<Item> selection;
}
mockMvc.perform(post("/somehwere/new").requestAttr("newObject", newObject)
@Controller
@RequestMapping(value = "/somewhere/new")
public class SomewhereController {
@RequestMapping(method = RequestMethod.POST)
public String post(
@ModelAttribute("newObject") NewObject newObject) {
// ...
}
// static import (optional)
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
// in your test method, populate your model attribute object (yes, works with nested properties)
BlogSetup bgs = new BlogSetup();
bgs.getBlog().setBlogTitle("Test Blog");
bgs.getUser().setEmail("admin.localhost@example.com");
bgs.getUser().setFirstName("Administrator");
bgs.getUser().setLastName("Localhost");
bgs.getUser().setPassword("password");
// finally put it together
mockMvc.perform(
postForm("/blogs/create", bgs, "blog.blogTitle", "user.email",
"user.firstName", "user.lastName", "user.password"))
.andExpect(status().isOk())
我已经推断,在构建表单时最好能够提到属性路径,因为我需要在测试中改变它。例如,我可能想检查是否在丢失的输入上出现验证错误,并且我将省略属性路径以模拟该条件。我还发现在@Before方法中构建模型属性更容易
BeanUtils来自commons BeanUtils:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
<scope>test</scope>
</dependency>
公地小海狸
公地小海狸
1.8.3
测试
我也有同样的问题,结果证明,通过使用JSON marshaller,解决方案相当简单。让控制器通过将
@modeldattribute(“newObject”)
更改为@RequestBody
来更改签名。像这样:
@Controller
@RequestMapping(value = "/somewhere/new")
public class SomewhereController {
@RequestMapping(method = RequestMethod.POST)
public String post(@RequestBody NewObject newObject) {
// ...
}
}
然后在测试中,您可以简单地说:
NewObject newObjectInstance = new NewObject();
// setting fields for the NewObject
mockMvc.perform(MockMvcRequestBuilders.post(uri)
.content(asJsonString(newObjectInstance))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON));
其中,asJsonString
方法只是:
public static String asJsonString(final Object obj) {
try {
final ObjectMapper mapper = new ObjectMapper();
final String jsonContent = mapper.writeValueAsString(obj);
return jsonContent;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
下面是我用来递归转换映射中对象字段的方法,可以与
MockHttpServletRequestBuilder
public static void objectToPostParams(final String key, final Object value, final Map<String, String> map) throws IllegalAccessException {
if ((value instanceof Number) || (value instanceof Enum) || (value instanceof String)) {
map.put(key, value.toString());
} else if (value instanceof Date) {
map.put(key, new SimpleDateFormat("yyyy-MM-dd HH:mm").format((Date) value));
} else if (value instanceof GenericDTO) {
final Map<String, Object> fieldsMap = ReflectionUtils.getFieldsMap((GenericDTO) value);
for (final Entry<String, Object> entry : fieldsMap.entrySet()) {
final StringBuilder sb = new StringBuilder();
if (!GenericValidator.isEmpty(key)) {
sb.append(key).append('.');
}
sb.append(entry.getKey());
objectToPostParams(sb.toString(), entry.getValue(), map);
}
} else if (value instanceof List) {
for (int i = 0; i < ((List) value).size(); i++) {
objectToPostParams(key + '[' + i + ']', ((List) value).get(i), map);
}
}
}
下面是ReflectionUtils
类
public final class ReflectionUtils {
public static List<Field> getAllFields(final List<Field> fields, final Class<?> type) {
if (type.getSuperclass() != null) {
getAllFields(fields, type.getSuperclass());
}
// if a field is overwritten in the child class, the one in the parent is removed
fields.addAll(Arrays.asList(type.getDeclaredFields()).stream().map(field -> {
final Iterator<Field> iterator = fields.iterator();
while(iterator.hasNext()){
final Field fieldTmp = iterator.next();
if (fieldTmp.getName().equals(field.getName())) {
iterator.remove();
break;
}
}
return field;
}).collect(Collectors.toList()));
return fields;
}
public static Map<String, Object> getFieldsMap(final GenericDTO genericDTO) throws IllegalAccessException {
final Map<String, Object> map = new HashMap<>();
final List<Field> fields = new ArrayList<>();
getAllFields(fields, genericDTO.getClass());
for (final Field field : fields) {
final boolean isFieldAccessible = field.isAccessible();
field.setAccessible(true);
map.put(field.getName(), field.get(genericDTO));
field.setAccessible(isFieldAccessible);
}
return map;
}
}
公共最终类ReflectionUtils{
公共静态列表getAllFields(最终列表字段,最终类类型){
if(type.getSuperclass()!=null){
getAllFields(字段,类型.getSuperclass());
}
//如果子类中的字段被覆盖,则父类中的字段将被删除
fields.addAll(Arrays.asList(type.getDeclaredFields()).stream().map(field->{
final Iterator Iterator=fields.Iterator();
while(iterator.hasNext()){
final字段fieldTmp=iterator.next();
if(fieldTmp.getName().equals(field.getName())){
iterator.remove();
打破
}
}
返回字段;
}).collect(Collectors.toList());