Spring MVC控制器测试:类型转换与ModelAndView断言错误排查
问题根源分析
你的测试遇到几个核心问题,我们逐一拆解:
1. @ModelAttribute 参数绑定逻辑错误
控制器里的@ModelAttribute("book") Book book是请求参数属性绑定,Spring MVC会从请求的键值对里提取book.id这类属性来构建Book对象,而不是直接接收整个Book的字符串或JSON。
- 第一次传
String.valueOf(book)本质是把Book的toString()结果(比如com.valentine.domain.Book@2761156e)当成参数值,Spring无法将这个字符串转成Long类型的id,所以触发类型转换错误。 - 后来传JSON字符串也不对:GET请求的参数是URL中的键值对,Spring不会自动把JSON字符串反序列化为Book对象,且你设置的
contentType(MediaType.TEXT_HTML)也不符合表单参数的要求。
2. 断言与控制器实际返回不匹配
控制器最终返回的是"forward:/bookDetail?id=" + book.getId(),但你测试里断言的是forward:/bookDetail?id=" + cartItem.getId(),两者完全不匹配,会导致断言失败。
3. Mock的用户名不匹配
你用@WithMockUser(username = "V")模拟用户,但mock的userService.findByUsername("valentine")查找的是"valentine",和当前用户名"V"不一致,会导致user为null,触发控制器里的NullPointerException捕获逻辑,后续业务代码不会执行,自然不会设置addBookSuccess属性,导致断言失败。
修正后的测试代码
针对上述问题逐一调整:
@Test @WithMockUser(username = "V", authorities = {"USER"}) public void addItemToShoppingCart() throws Exception { CartItem cartItem = new CartItem(); String qty = "2"; Book book = new Book(); book.setActive(true); book.setId(1L); book.setInStockNumber(5); // 确保库存大于购买数量,避免触发库存不足分支 User user = new User(); // 修正:mock的用户名要和@WithMockUser保持一致 expect(userService.findByUsername("V")).andReturn(user); expect(bookService.findOne(anyLong())).andReturn(book); expect(cartItemService.addBookToCartItem(book, user, Integer.parseInt(qty))).andReturn(cartItem); replay(); mockMvc .perform(get("/shoppingCart/addItem") .accept(MediaType.TEXT_HTML) // GET请求可设置表单编码类型,无需TEXT_HTML .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 正确传递Book的属性参数,Spring会自动绑定为Book对象 .param("book.id", String.valueOf(book.getId())) .param("qty", qty)) .andExpect(model().attributeExists("addBookSuccess")) .andExpect(model().attribute("addBookSuccess", true)) // 修正:视图名称要和控制器返回的一致,使用book的id .andExpect(view().name("forward:/bookDetail?id=" + book.getId())) .andReturn(); }
额外注意事项
- 若要测试库存不足的场景,可设置
book.setInStockNumber(1),然后断言model().attribute("notEnoughStock", true)。 - 控制器里捕获
NullPointerException但无任何处理逻辑,建议至少添加日志输出,否则排查问题会很困难。 - 测试中多余的
book.getId()调用可以直接删除。
内容的提问来源于stack exchange,提问作者valen ezu
相关产品推荐
相关产品推荐

