如何测试含AuthenticationManagerBuilder与SecurityContextHolder的控制器?测试报错解决
解决AuthenticationManagerBuilder测试空指针问题并完成JWT响应验证
错误原因分析
- Mock的AuthenticationManagerBuilder内部字段未初始化:
AuthenticationManagerBuilder继承自AbstractSecurityBuilder,后者包含私有原子布尔字段building。Mockito默认不会初始化这类非公共字段,调用getObject()时会触发NullPointerException。 - 未Mock TokenProvider依赖:控制器中调用
tokenProvider.createToken()生成JWT,但测试未注入该Mock对象,会导致空指针。 - SecurityContextHolder静态Mock方式错误:直接
mock(SecurityContextHolder.class)无法正确拦截静态方法调用,需用mockStatic处理。 - 链式Mockito调用无效:
when(authenticationManagerBuilder.getObject().authenticate(...))实际是对getObject()返回的临时ProviderManager设置Stub,而非控制器实际调用的实例,导致Stub不生效。
修正后的测试代码
@ExtendWith(MockitoExtension.class) public class AuthControllerTest { @Mock private AuthenticationManagerBuilder authenticationManagerBuilder; @Mock private AuthenticationManager authenticationManager; // 新增Mock AuthenticationManager @Mock private TokenProvider tokenProvider; // 新增Mock TokenProvider @InjectMocks private AuthController authController; @Test public void shouldReturnUserDtoAndBearerToken() { // 1. 测试数据准备 String username = "username"; String password = "password"; String mockJwt = "mock-jwt-token"; AuthDto authDto = AuthDto.builder().username(username).password(password).build(); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); Authentication authenticated = new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList()); // 2. Stub依赖调用 // 让AuthenticationManagerBuilder返回Mock的AuthenticationManager Mockito.when(authenticationManagerBuilder.getObject()).thenReturn(authenticationManager); // Stub认证逻辑,直接返回已认证对象 Mockito.when(authenticationManager.authenticate(authenticationToken)).thenReturn(authenticated); // StubJWT生成逻辑,返回预设的Mock令牌 Mockito.when(tokenProvider.createToken(authenticated)).thenReturn(mockJwt); // 3. 静态Mock SecurityContextHolder(可选,若不需要验证上下文设置可省略) try (MockedStatic<SecurityContextHolder> mockedStatic = Mockito.mockStatic(SecurityContextHolder.class)) { SecurityContext securityContext = Mockito.mock(SecurityContext.class); mockedStatic.when(SecurityContextHolder::getContext).thenReturn(securityContext); // 4. 执行测试方法 ResponseEntity<String> responseEntity = authController.login(authDto); // 5. 验证结果 Assertions.assertTrue(responseEntity.getHeaders().containsKey("Authorization")); List<String> authHeaders = responseEntity.getHeaders().get("Authorization"); Assertions.assertEquals(1, authHeaders.size()); String bearerToken = authHeaders.get(0); Assertions.assertTrue(bearerToken.startsWith("Bearer ")); Assertions.assertEquals("Bearer " + mockJwt, bearerToken); } } }
关键修正点说明
- Mock AuthenticationManager:避免直接实例化
ProviderManager带来的内部字段未初始化问题,通过StubauthenticationManagerBuilder.getObject()返回Mock的AuthenticationManager,再Stub其authenticate方法跳过真实认证逻辑。 - 新增TokenProvider Mock:控制器依赖该类生成JWT,必须Mock并Stub
createToken方法返回预设令牌,才能验证响应头的正确性。 - 正确静态Mock SecurityContextHolder:使用
MockedStatic(Mockito 3.4+支持)包裹静态类的调用,确保静态方法被正确拦截,若不需要验证上下文设置,也可直接删除这部分代码(SecurityContextHolder默认会创建空上下文,不会影响测试)。 - 明确Stub层级:拆分链式调用为两步,先Stub
getObject()返回Mock的AuthenticationManager,再Stub该Manager的authenticate方法,确保Stub作用到控制器实际调用的实例。
内容的提问来源于stack exchange,提问作者bleschunov
相关产品推荐
相关产品推荐

