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

JUnit与Spring Test问题:期望调用ObjectMapper方法却未执行

单元测试LoginService的logInUser方法时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(推荐)

这是最规范的做法,符合依赖注入原则,也便于测试:

  1. 更新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());
        }
    }
}
  1. 更新测试代码,将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实例。但这种方式会引入额外依赖,且测试复杂度更高,仅作为临时解决方案:

  1. 引入PowerMock依赖(根据构建工具添加,比如Maven);
  2. 配置测试类使用PowerMockRunner,并指定要mock的类;
  3. 在测试中mocknew ObjectMapper()返回你的mock实例。

内容的提问来源于stack exchange,提问作者Don Fleym

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 12:15:49