You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:38:40