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
相关产品推荐
相关产品推荐

