如何用MockMvc发送含对象数组的POST请求适配@ModelAttribute控制器
MockMvc测试POST接口传递对象数组参数报400错误排查与解决
问题现象
测试时抛出断言错误:
java.lang.AssertionError: Status expected:<201> but was:<400>
MockHttpServletResponse未返回错误信息,推测问题出在**对象数组参数lecture_values**的传递上,该参数在请求模型中定义为List<Map<String, Object>>。
预期请求体
{ "study_class_id": 1, "lecture_values": [ { "topic": 1, "content": "Lorem ipsum" }, { "topic": 2, "content": "Lorem ipsum" } ] }
控制器代码
@PostMapping(value = "/lecture") public ResponseEntity<LecturesResource> addLecture( @Valid @ModelAttribute("Lecture") LectureAddRequestParam param, BindingResult bindingResult, Principal principal, HttpServletRequest request, HttpServletResponse response, HttpSession session) { return processResult; }
请求参数模型
public class LectureAddRequestParam { @NotNull @Min(1) private int study_class_id; @NotNull private List<Map<String, Object>> lecture_values; // Getter Setter.. }
当前测试代码(报错版本)
// Prepare lecture values param JSONObject lecture1 = new JSONObject(); lecture1.put("topic", 1); lecture1.put("content", "Lorem ipsum"); JSONObject lecture2 = new JSONObject(); lecture2.put("topic", 2); lecture2.put("content", "Lorem ipsum"); JSONArray lectureValues = new JSONArray(); lectureValues.add(lecture1); lectureValues.add(lecture2); var responseContent = mockMvc .perform( post("/lecture") .with(csrf()) .param("study_class_id", Integer.toString(studyClassId)) .param("lecture_values", lectureValues.toString())) .andDo(log()) .andExpect(status().isCreated()) .andReturn() .getResponse() .getContentAsString();
前端可成功调用的代码
$.ajax({ url: '/lecture', type: 'POST', timeout: this.TIMEOUT_LIMIT, headers: requestHeader, data: { study_class_id: study_class_id, lecture_values: lecture_values.toJSON() } });
注:
lecture_values是Backbone.js集合,调用toJSON()转换为数组格式。
其他无效尝试
- 创建包含
study_class_id和lecture_values数组的lectureJSONObject,调用flashAttr("lecture", lectureJSONObject) - 创建包含
study_class_id和转换为List<Map<String, Object>>的lecture_values的lectureObject,调用flashAttr("lecture", lectureObject)
问题原因与解决方案
原因分析
当前测试用.param()传递数组参数时,是把JSON数组转成字符串传递,Spring无法自动将这个字符串解析为List<Map<String, Object>>;而前端jQuery的ajax会自动将数组参数按照表单数组格式(如lecture_values[0].topic=1&lecture_values[0].content=xxx)编码,Spring的@ModelAttribute可以正确解析这种格式。
解决方案
方案1:按表单数组格式构造参数
直接模拟前端的参数传递方式,逐个设置数组元素的属性:
var responseContent = mockMvc .perform( post("/lecture") .with(csrf()) .param("study_class_id", Integer.toString(studyClassId)) // 第一个数组元素 .param("lecture_values[0].topic", "1") .param("lecture_values[0].content", "Lorem ipsum") // 第二个数组元素 .param("lecture_values[1].topic", "2") .param("lecture_values[1].content", "Lorem ipsum")) .andDo(log()) .andExpect(status().isCreated()) .andReturn() .getResponse() .getContentAsString();
方案2:使用RequestBuilder构造完整表单数据
如果数组元素较多,可以用LinkedMultiValueMap构造参数:
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("study_class_id", Integer.toString(studyClassId)); // 添加第一个元素 params.add("lecture_values[0].topic", "1"); params.add("lecture_values[0].content", "Lorem ipsum"); // 添加第二个元素 params.add("lecture_values[1].topic", "2"); params.add("lecture_values[1].content", "Lorem ipsum"); var responseContent = mockMvc .perform( post("/lecture") .with(csrf()) .params(params)) .andDo(log()) .andExpect(status().isCreated()) .andReturn() .getResponse() .getContentAsString();
方案3:改用@RequestBody接收JSON(可选)
如果允许修改控制器,可以将@ModelAttribute改为@RequestBody,测试时直接发送JSON请求体:
// 修改控制器 @PostMapping(value = "/lecture") public ResponseEntity<LecturesResource> addLecture( @Valid @RequestBody LectureAddRequestParam param, // 改为@RequestBody BindingResult bindingResult, Principal principal, HttpServletRequest request, HttpServletResponse response, HttpSession session) { return processResult; } // 测试代码 String requestBody = "{\"study_class_id\":1,\"lecture_values\":[{\"topic\":1,\"content\":\"Lorem ipsum\"},{\"topic\":2,\"content\":\"Lorem ipsum\"}]}"; var responseContent = mockMvc .perform( post("/lecture") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) .content(requestBody)) .andDo(log()) .andExpect(status().isCreated()) .andReturn() .getResponse() .getContentAsString();
内容的提问来源于stack exchange,提问作者MMT Yuma
相关产品推荐
相关产品推荐

