Mockito PUT请求单元测试断言错误:JSON path $.name值不符
测试PUT接口单元测试失败:返回旧用户数据而非更新后的数据
测试UserController中声明的PUT接口时,执行testUpdateUser()测试方法抛出java.lang.AssertionError:JSON路径$.name预期值为"Bob",但实际值为"Alice"。
单元测试代码
@Test public void testUpdateUser() throws Exception { User user = new User(1, "Alice", "alice", "alice@example.com"); when(UserDao.getUserById(1)).thenReturn(Collections.singletonList(user)); // Retrieve the user from the mock database User updatedUser = new User(1, "Bob", "bob", "bob@example.com"); when(UserDao.updateUser(updatedUser, 1)).thenReturn(1); ; // Update the user in the mock database ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(updatedUser); MockHttpServletRequestBuilder requestBuilder = put("/users/1") .contentType(MediaType.APPLICATION_JSON) .content(json); mockMvc.perform(requestBuilder) .andExpect(status().isOk()) .andExpect(jsonPath("$.id", is(1))) .andExpect(jsonPath("$.name", is("Bob"))) .andExpect(jsonPath("$.username", is("bob"))) .andExpect(jsonPath("$.email", is("bob@example.com"))); verify(UserDao).updateUser(updatedUser, 1); verify(template).convertAndSend("/topic/update", updatedUser); }
完整错误日志
HTTP Method = PUT Request URI = /users/1 Parameters = {} Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"64"] Body = {"id":1,"name":"Bob","username":"bob","email":"bob@example.com"} Session Attrs = {} Handler: Type = com.example.padar.controller.UserController Method = com.example.padar.controller.UserController#editUsers(int, User, HttpServletRequest) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"application/json", Content-Length:"70"] Content type = application/json Body = {"id":1,"name":"Alice","username":"alice","email":"alice@example.com"} Forwarded URL = null Redirected URL = null Cookies = [] java.lang.AssertionError: JSON path "$.name" Expected: is "Bob" but: was "Alice" <Click to see difference> at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20) at org.springframework.test.util.JsonPathExpectationsHelper.assertValue(JsonPathExpectationsHelper.java:74) at org.springframework.test.web.servlet.result.JsonPathResultMatchers.lambda$value$0(JsonPathResultMatchers.java:87) at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:212) at com.example.padar.controller.UserControllerTest.testUpdateUser(UserControllerTest.java:113) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) at java.base/java.lang.reflect.Method.invoke(Method.java:577) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84) at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53) at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
UserController代码
@PutMapping("/users/{id}") public ResponseEntity<?> editUsers(@PathVariable int id,@RequestBody User user, HttpServletRequest request){ LOG.info( "FINISHED PROCESSING : METHOD={}; REQUESTURL={}; ID={}; REQUESTBODY={}; OLDUSER={};", request.getMethod(), request.getRequestURI(), request.getSession().getId(), user.toString(), UserDao.getUserById(id).get(0).toString()); UserDao.updateUser(user, id); template.convertAndSend("/topic/update", UserDao.getUserById(id)); return ResponseEntity.ok(UserDao.getUserById(id).get(0)); }
UserDao代码
public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; private static final String SQL_GET_ALL_USERS = "SELECT * FROM user"; private static final String SQL_GET_USER_BY_ID = "SELECT id,name,username,email FROM user where id = ?"; private static final String SQL_GET_POSTED_USER = "SELECT id,username,email FROM user ORDER BY id DESC LIMIT 1 "; private static final String SQL_DELETE_USER_BY_ID = "DELETE FROM user WHERE id=?"; private static final String SQL_MAKE_NEW_USER = "INSERT INTO user(name,username,email) VALUES(?,?,?)"; private static final String SQL_UPDATE_USER = "UPDATE user SET name = ?, username = ?, email = ? WHERE id=?"; public List<User> getAllUsers() { return jdbcTemplate.query(SQL_GET_ALL_USERS, new UserMapper()); } public int deleteUserById(int id) { return jdbcTemplate.update(SQL_DELETE_USER_BY_ID, id); } public List<User> getUserById(int id) {return jdbcTemplate.query(SQL_GET_USER_BY_ID,new UserMapper(),id); } public List<User> showPostedUser() {return jdbcTemplate.query(SQL_GET_POSTED_USER,new UserMapper()); } public int addUser(User user) { jdbcTemplate.update(SQL_MAKE_NEW_USER, user.getName(), user.getUsername(), user.getEmail()); return 1; } public int updateUser(User user, int id) { return jdbcTemplate.update(SQL_UPDATE_USER, user.getName(), user.getUsername(), user.getEmail(), id); } }
问题原因与解决方法
原因
单元测试中仅Mock了getUserById(1)的第一次调用,返回初始的Alice用户,但在Controller逻辑中:
- 日志打印时调用一次
getUserById(1) - 更新后调用
template.convertAndSend时再次调用getUserById(1) - 返回响应时第三次调用
getUserById(1)
Mock未处理后续调用,导致所有getUserById(1)都返回初始数据,最终响应返回旧用户信息。另外,测试中verify(template).convertAndSend("/topic/update", updatedUser)与Controller实际调用的template.convertAndSend("/topic/update", UserDao.getUserById(id))不一致,也会导致验证失败。
解决方法
更新Mock的
getUserById逻辑,让后续调用返回更新后的用户:// 第一次调用返回旧用户,之后的调用返回更新后的用户 when(UserDao.getUserById(1)) .thenReturn(Collections.singletonList(user)) .thenReturn(Collections.singletonList(updatedUser)) .thenReturn(Collections.singletonList(updatedUser));或者使用更灵活的
thenAnswer处理多次调用:AtomicInteger callCount = new AtomicInteger(0); when(UserDao.getUserById(1)).thenAnswer(invocation -> { if (callCount.getAndIncrement() == 0) { return Collections.singletonList(user); } else { return Collections.singletonList(updatedUser); } });修正verify的验证逻辑,与Controller实际调用保持一致:
verify(template).convertAndSend("/topic/update", UserDao.getUserById(1));规范Controller注入方式:方法参数中的
HttpServletRequest不需要加@Autowired,Spring会自动注入,修改为:public ResponseEntity<?> editUsers(@PathVariable int id,@RequestBody User user, HttpServletRequest request){
内容的提问来源于stack exchange,提问作者user19640250
相关产品推荐
相关产品推荐

