测试UserController时返回空而非预期对象问题排查
问题描述
测试UserController的创建用户和获取用户方法时,出现断言失败错误,输出如下:
org.opentest4j.AssertionFailedError:
Expected :UserDTO(id=1, name=Steve, email=steve@mail.com, group=null)
Actual :
测试类代码如下:
@WebMvcTest(UserController.class) class UserControllerTest { @MockBean private UserInterface userInterface; @Autowired private MockMvc mockMvc; @Test void create() throws Exception { UserCreationDTO user = new UserCreationDTO(1L, "Steve", "steve@mail.com", "password"); Mockito.when(userInterface.create(user)).thenReturn(user); RequestBuilder request = MockMvcRequestBuilders.post("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(String.valueOf(user)); MvcResult result = mockMvc.perform(request).andReturn(); assertEquals(user, result.getResponse().getContentAsString()); } @Test void getUser() throws Exception { UserDTO userDTO = new UserDTO(1L, "Steve", "steve@mail.com", null); Mockito.when(userInterface.getUser(1L)).thenReturn(userDTO); RequestBuilder request = MockMvcRequestBuilders.get("/api/users/1"); MvcResult result = mockMvc.perform(request).andReturn(); assertEquals(userDTO, result.getResponse().getContentAsString()); } }
错误原因及修复方案
1. 请求内容序列化错误
用String.valueOf(user)生成POST请求的JSON内容,本质是调用对象的toString()方法,生成的不是标准JSON格式,导致Controller无法解析请求,最终返回空内容。
修复:使用ObjectMapper将对象序列化为标准JSON字符串:
// 先注入ObjectMapper @Autowired private ObjectMapper objectMapper; // 在create测试中替换content部分 .content(objectMapper.writeValueAsString(user))
2. Mockito参数匹配问题
Mockito.when(userInterface.create(user)).thenReturn(user)中直接传入user对象,Mockito会进行引用相等匹配。但Controller接收请求后会反序列化出一个新的UserCreationDTO对象,和测试里的user不是同一个引用,导致Mock逻辑不生效,返回null,接口最终返回空。
修复:使用参数匹配器any(UserCreationDTO.class)匹配任意该类型的对象:
Mockito.when(userInterface.create(Mockito.any(UserCreationDTO.class))).thenReturn(user);
3. 断言逻辑错误
直接把UserDTO/UserCreationDTO对象和result.getResponse().getContentAsString()(JSON字符串)做相等比较,对象和字符串永远不可能相等,必然断言失败。
修复:推荐使用MockMvc内置的JSON断言方法(更简洁):
mockMvc.perform(request) .andExpect(MockMvcResultMatchers.content().json(objectMapper.writeValueAsString(user)));
也可以将返回的JSON反序列化为对象后再比较:
String responseContent = result.getResponse().getContentAsString(); UserCreationDTO responseUser = objectMapper.readValue(responseContent, UserCreationDTO.class); assertEquals(user, responseUser);
4. getUser测试的额外注意点
getUser测试中,除了上述断言问题,需添加accept(MediaType.APPLICATION_JSON)确保接口返回JSON格式,同时确认Controller的路径变量类型和测试中的1L一致,避免参数匹配失败。
修改后的完整测试类示例
@WebMvcTest(UserController.class) class UserControllerTest { @MockBean private UserInterface userInterface; @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @Test void create() throws Exception { UserCreationDTO user = new UserCreationDTO(1L, "Steve", "steve@mail.com", "password"); Mockito.when(userInterface.create(Mockito.any(UserCreationDTO.class))).thenReturn(user); RequestBuilder request = MockMvcRequestBuilders.post("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(user)); mockMvc.perform(request) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().json(objectMapper.writeValueAsString(user))); } @Test void getUser() throws Exception { UserDTO userDTO = new UserDTO(1L, "Steve", "steve@mail.com", null); Mockito.when(userInterface.getUser(1L)).thenReturn(userDTO); RequestBuilder request = MockMvcRequestBuilders.get("/api/users/1") .accept(MediaType.APPLICATION_JSON); mockMvc.perform(request) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().json(objectMapper.writeValueAsString(userDTO))); } }
内容的提问来源于stack exchange,提问作者m_novak

