JUnit与Spring Test问题:期望调用ObjectMapper方法却未执行
我是测试新手,在对应用中的LoginService类的logInUser方法进行单元测试时,遇到了无法理解的错误。以下是待测试代码、测试代码及报错信息:
待测试的LoginService代码
@Service @Slf4j public class LoginService { AuthenticationManager authenticationManager; JwtService jwtService; @Autowired LoginService(AuthenticationManager authenticationManager, JwtService jwtService){ this.jwtService = jwtService; this.authenticationManager = authenticationManager; } public void logInUser(String username, String password, HttpServletResponse response){ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username,password); try{ Authentication authUser = authenticationManager.authenticate(usernamePasswordAuthenticationToken); String accessToken = jwtService.createAccessJwtToken(username); String refreshToken = jwtService.createRefreshJwtToken(username); Map<String, String> tokens = new HashMap<>(); tokens.put("accessToken",accessToken); tokens.put("refreshToken",refreshToken); ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(response.getWriter(),tokens); }catch (Exception e){ throw new BadCredentialsException(e.getMessage()); } } }
测试代码
@ExtendWith(MockitoExtension.class) class LoginServiceTest { @Mock AuthenticationManager authenticationManager; @Mock JwtService jwtService; @Mock HttpServletResponse httpServletResponse; @Mock PrintWriter printWriter; @Mock Authentication authUser; @Mock ObjectMapper objectMapper; LoginService loginService; @BeforeEach void setUp() { this.loginService = new LoginService(authenticationManager,jwtService); } @Test void logInUser() throws Exception{ String email = "test@test.com"; String password = "test1234"; Map<String,String> expectedTokens = new HashMap<>(); String expectedCreatedAccessToken = "accessToken"; String expectedCreatedRefreshToken = "refreshToken"; expectedTokens.put("accessToken",expectedCreatedAccessToken); expectedTokens.put("refreshToken",expectedCreatedRefreshToken); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email,password); when(authenticationManager.authenticate(authToken)).thenReturn(authUser); when(jwtService.createAccessJwtToken(email)).thenReturn(expectedTokens.get("accessToken")); when(jwtService.createRefreshJwtToken(email)).thenReturn(expectedTokens.get("refreshToken")); when(httpServletResponse.getWriter()).thenReturn(printWriter); doNothing().when(objectMapper).writeValue(printWriter, expectedTokens); this.loginService.logInUser(email,password,httpServletResponse); verify(authenticationManager).authenticate(authToken); verify(httpServletResponse).getWriter(); verify(jwtService).createAccessJwtToken(email); verify(jwtService).createRefreshJwtToken(email); verify(objectMapper).writeValue(printWriter,expectedTokens); } }
报错信息
Wanted but not invoked: objectMapper.writeValue(
printWriter,
{"accessToken" = "accessToken", "refreshToken" = "refreshToken"} );-> at com.project.requests.login.service.LoginServiceTest.logInUser(LoginServiceTest.java:67)
Actually, there were zero interactions with this mock.
我尝试调整doNothing().when(objectMapper).writeValue(printWriter, expectedTokens);语句,包括移除、修改参数或使用Mockito的参数匹配器,但都无法解决问题,希望得到帮助。
问题根源
你的LoginService中是直接通过new ObjectMapper()创建实例,而测试里@Mock的ObjectMapper并没有被注入到服务类中。实际运行时,服务使用的是自己创建的ObjectMapper实例,和测试中的mock完全无关,自然无法验证到交互行为。
修复方案
方案一:修改LoginService为依赖注入ObjectMapper(推荐)
这是最规范的做法,符合依赖注入原则,也便于测试:
- 更新LoginService代码,注入ObjectMapper:
@Service @Slf4j public class LoginService { private final AuthenticationManager authenticationManager; private final JwtService jwtService; private final ObjectMapper objectMapper; @Autowired public LoginService(AuthenticationManager authenticationManager, JwtService jwtService, ObjectMapper objectMapper) { this.authenticationManager = authenticationManager; this.jwtService = jwtService; this.objectMapper = objectMapper; } public void logInUser(String username, String password, HttpServletResponse response){ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username,password); try{ Authentication authUser = authenticationManager.authenticate(usernamePasswordAuthenticationToken); String accessToken = jwtService.createAccessJwtToken(username); String refreshToken = jwtService.createRefreshJwtToken(username); Map<String, String> tokens = new HashMap<>(); tokens.put("accessToken",accessToken); tokens.put("refreshToken",refreshToken); // 使用注入的objectMapper,不再手动new objectMapper.writeValue(response.getWriter(),tokens); }catch (Exception e){ throw new BadCredentialsException(e.getMessage()); } } }
- 更新测试代码,将mock的ObjectMapper传入LoginService构造方法:
@ExtendWith(MockitoExtension.class) class LoginServiceTest { @Mock AuthenticationManager authenticationManager; @Mock JwtService jwtService; @Mock HttpServletResponse httpServletResponse; @Mock PrintWriter printWriter; @Mock Authentication authUser; @Mock ObjectMapper objectMapper; LoginService loginService; @BeforeEach void setUp() { // 传入mock的objectMapper this.loginService = new LoginService(authenticationManager,jwtService, objectMapper); } @Test void logInUser() throws Exception{ String email = "test@test.com"; String password = "test1234"; Map<String,String> expectedTokens = new HashMap<>(); String expectedCreatedAccessToken = "accessToken"; String expectedCreatedRefreshToken = "refreshToken"; expectedTokens.put("accessToken",expectedCreatedAccessToken); expectedTokens.put("refreshToken",expectedCreatedRefreshToken); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email,password); when(authenticationManager.authenticate(authToken)).thenReturn(authUser); when(jwtService.createAccessJwtToken(email)).thenReturn(expectedTokens.get("accessToken")); when(jwtService.createRefreshJwtToken(email)).thenReturn(expectedTokens.get("refreshToken")); when(httpServletResponse.getWriter()).thenReturn(printWriter); doNothing().when(objectMapper).writeValue(printWriter, expectedTokens); this.loginService.logInUser(email,password,httpServletResponse); verify(authenticationManager).authenticate(authToken); verify(httpServletResponse).getWriter(); verify(jwtService).createAccessJwtToken(email); verify(jwtService).createRefreshJwtToken(email); verify(objectMapper).writeValue(printWriter,expectedTokens); } }
方案二:使用PowerMock mock静态new调用(不推荐,增加复杂度)
如果无法修改原服务代码,可以使用PowerMock来拦截new ObjectMapper()的调用,返回测试中的mock实例。但这种方式会引入额外依赖,且测试复杂度更高,仅作为临时解决方案:
- 引入PowerMock依赖(根据构建工具添加,比如Maven);
- 配置测试类使用PowerMockRunner,并指定要mock的类;
- 在测试中mock
new ObjectMapper()返回你的mock实例。
内容的提问来源于stack exchange,提问作者Don Fleym

