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

Spring Boot中基于Spring Security的登录服务测试异常排查

问题分析与修复

异常原因解析

1. 空指针异常(NullPointerException)

初始测试中直接调用了authenticationManager.authenticate(authToken),但测试环境里authenticationManager是Mock对象,此时还未设置mock行为,调用后返回null,后续执行auth.getPrincipal()时触发空指针。同时mock行为的设置顺序错误,应该先定义mock返回值,再执行被测试方法。

2. 类型转换异常(ClassCastException)

修改后的测试中,返回的authToken是用用户名和密码直接构造的UsernamePasswordAuthenticationToken,它的principal属性是字符串类型的用户名,而非JwtUserDetails实例。在AuthService中尝试将auth.getPrincipal()强转为JwtUserDetails时,自然触发类型转换错误。

修复后的测试代码

@Test
void shouldLogin() {
    // Given
    AdminLoginRequest loginRequest = new UserBuilder().getLoginRequest();

    // 构造User和对应的JwtUserDetails实例
    User user = User.builder()
            .username(loginRequest.getUsername())
            .password(loginRequest.getPassword())
            .userRole(UserRole.ROLE_ADMIN)
            .id(1L)
            .build();
    JwtUserDetails jwtUserDetails = new JwtUserDetails(user);

    // 构造包含JwtUserDetails的Authentication对象
    Authentication auth = new UsernamePasswordAuthenticationToken(
            jwtUserDetails, 
            loginRequest.getPassword(), 
            jwtUserDetails.getAuthorities()
    );

    AuthResponse expectedAuthResponse = AuthResponse.builder()
            .expireDate(new Date().getTime() + 120000)
            .refreshToken("refreshToken")
            .message("success")
            .accessToken("access-token") // AuthService中会自动添加"Bearer "前缀,此处无需提前添加
            .roles(List.of(UserRole.ROLE_ADMIN.name()))
            .username(user.getUsername())
            .build();

    RefreshToken refreshToken = RefreshToken.builder()
            .token(expectedAuthResponse.getRefreshToken())
            .build();

    // When:先设置所有Mock的行为逻辑
    when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class)))
            .thenReturn(auth);
    when(jwtTokenProvider.generateJwtToken(eq(auth)))
            .thenReturn(expectedAuthResponse.getAccessToken());
    when(refreshTokenService.createRefreshToken(eq(jwtUserDetails.getId())))
            .thenReturn(refreshToken);

    // 执行被测试的登录方法
    AuthResponse actualAuthResponse = authService.login(loginRequest);

    // Then:断言结果匹配
    assertThat(actualAuthResponse).isNotNull();
    assertEquals("Bearer " + expectedAuthResponse.getAccessToken(), actualAuthResponse.getAccessToken());
    assertEquals(expectedAuthResponse.getUsername(), actualAuthResponse.getUsername());
    assertEquals(expectedAuthResponse.getRefreshToken(), actualAuthResponse.getRefreshToken());
    assertEquals(expectedAuthResponse.getMessage(), actualAuthResponse.getMessage());
    assertEquals(expectedAuthResponse.getRoles(), actualAuthResponse.getRoles());
}

关键修复点说明

  • 构造合法的Authentication对象:使用包含JwtUserDetails的UsernamePasswordAuthenticationToken,确保getPrincipal()返回目标类型实例,彻底避免类型转换错误。
  • 调整Mock执行顺序:先定义所有Mock对象的返回行为,再调用被测试的login方法,确保Mock逻辑生效。
  • 对齐业务逻辑:注意AuthService会给accessToken添加"Bearer "前缀,预期值中无需提前添加,断言时需对应此逻辑。
  • 避免触发真实认证:测试中不直接调用authenticationManager.authenticate()的真实逻辑,完全通过Mock模拟返回值,避免测试环境依赖问题。

内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 08:32:51