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

如何测试含AuthenticationManagerBuilder与SecurityContextHolder的控制器?测试报错解决

解决AuthenticationManagerBuilder测试空指针问题并完成JWT响应验证

错误原因分析

  1. Mock的AuthenticationManagerBuilder内部字段未初始化:AuthenticationManagerBuilder继承自AbstractSecurityBuilder,后者包含私有原子布尔字段building。Mockito默认不会初始化这类非公共字段,调用getObject()时会触发NullPointerException。
  2. 未Mock TokenProvider依赖:控制器中调用tokenProvider.createToken()生成JWT,但测试未注入该Mock对象,会导致空指针。
  3. SecurityContextHolder静态Mock方式错误:直接mock(SecurityContextHolder.class)无法正确拦截静态方法调用,需用mockStatic处理。
  4. 链式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并StubcreateToken方法返回预设令牌,才能验证响应头的正确性。
  • 正确静态Mock SecurityContextHolder:使用MockedStatic(Mockito 3.4+支持)包裹静态类的调用,确保静态方法被正确拦截,若不需要验证上下文设置,也可直接删除这部分代码(SecurityContextHolder默认会创建空上下文,不会影响测试)。
  • 明确Stub层级:拆分链式调用为两步,先StubgetObject()返回Mock的AuthenticationManager,再Stub该Manager的authenticate方法,确保Stub作用到控制器实际调用的实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 04:20:17