Spring Boot Test测试Controller报model attribute不存在
问题说明
对返回带模型数据的视图接口做单元测试时,testInitUpdateAuthor() 方法执行过程中无法找到指定名称的author模型属性,其余接口的模型、属性相关测试均可正常运行,涉及代码如下:
@Slf4j @Controller public class AuthorController { private final AuthorService authorService; private final String CREATEORUPDATEFORM = "author/createOrUpdateAuthor"; public AuthorController(AuthorService authorService) { this.authorService = authorService; } @GetMapping("/author/{id}/update") public String updateAuthor(@PathVariable("id") Long id, Model model) { model.addAttribute("author", authorService.findById(id)); return CREATEORUPDATEFORM; } }
@ExtendWith(MockitoExtension.class) @SpringBootTest class AuthorControllerTest { MockMvc mockMvc; @Mock AuthorService authorService; @InjectMocks AuthorController authorController; @BeforeEach void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(authorController).build(); } @Test void getIndex() throws Exception { mockMvc.perform(get("/author/index")) .andExpect(status().isOk()) .andExpect(view().name("author/index")) .andExpect(model().attributeExists("authors")); } @Test void testInitUpdateAuthor() throws Exception { when(authorService.findById(anyLong())).thenReturn(Author.builder().id(1L).build()); mockMvc.perform(get("/author/1/update")) .andExpect(status().isOk()) .andExpect(view().name("author/createOrUpdateAuthor")) .andExpect(model().attributeExists("author")); } }
排查建议
按以下优先级逐步定位即可:
- 先确认请求是否真的命中了目标接口方法
临时去掉model().attributeExists("author")的断言,在请求链式调用后加上.andDo(print())打印全量执行日志,重点看输出中Handler字段是否为AuthorController#updateAuthor(Long, Model):- 如果未命中目标方法,优先检查静态导入的
get请求构建方法是否为Spring Test提供的(很多人会错导RestAssured等其他框架的同名方法导致请求异常);如果项目配置了全局上下文路径server.servlet.context-path,standaloneSetup模式默认不会读取该配置,要确认实际请求路径和接口映射路径完全匹配。
- 如果未命中目标方法,优先检查静态导入的
- 检查模型属性实际的存储状态
看打印日志中ModelAndView节点下的model属性列表:- 如果列表里完全没有
author属性:检查AuthorController类上是否加了@SessionAttributes("author")注解,将该属性标记为了会话级别存储。standaloneSetup构建的MockMvc默认不会自动把会话属性暴露到request模型中,这种情况需要在perform请求时手动补充.sessionAttr("author", new Author()),或者调整测试配置。 - 如果
author属性存在但值为null:检查anyLong()参数匹配器的导入包是否正确,错导非Mockito包的参数匹配器会导致Mock触发条件不生效,authorService.findById()实际返回null;另外可以在setUp方法中打印authorController内部持有的authorService实例,确认@InjectMocks真的把Mock的Service实例注入进去了,没有出现注入失败的情况。
- 如果列表里完全没有
- 解决测试配置冲突问题
当前测试类同时使用了@SpringBootTest(加载Spring完整上下文)和Mockito standalone单元测试模式,两套实例管理逻辑很容易出现混淆:- 如果要做纯Controller层单元测试(不加载Spring上下文),直接删掉类上的
@SpringBootTest注解,只保留@ExtendWith(MockitoExtension.class)即可,避免Spring容器生成的Controller实例干扰手动注入的Mock实例。 - 如果要做Web层集成测试,就不要用
standaloneSetup手动构建MockMvc,改用@AutoConfigureMockMvc注解直接注入Spring容器管理的MockMvc实例,同时用@MockBean注解替换容器内的真实AuthorService,不要再用@Mock+@InjectMocks的组合。
- 如果要做纯Controller层单元测试(不加载Spring上下文),直接删掉类上的
内容的提问来源于stack exchange,提问作者MikesMikes
相关产品推荐
相关产品推荐

