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

无法mock继承WebSecurityConfigurerAdapter的WebConfig类,求JUnit测试方案

解决方案

问题根源

原测试无法正常验证逻辑有两个核心原因:

  • WebConfig中依赖的UserDetailsService、JwtRequestFilter未被Mock,@InjectMocks无法完成完整的依赖注入,会触发空指针异常
  • HttpSecurity是链式调用API,Mockito默认Mock对象的方法返回值为null,调用http.csrf().disable()时第一步返回null就会中断调用链,无法走完完整的配置逻辑

修正后的单元测试实现

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.test.util.ReflectionTestUtils;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class WebConfigTest {

    private WebConfig webConfig;

    @Mock
    private UserDetailsService userDetailsService;

    @Mock
    private JwtRequestFilter jwtRequestFilter;

    @Mock
    private AuthenticationManagerBuilder auth;

    @Mock
    private HttpSecurity http;
    
    // 提前Mock链式调用需要的返回对象
    @Mock
    private CsrfConfigurer<HttpSecurity> csrfConfigurer;
    @Mock
    private ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry interceptUrlRegistry;

    @BeforeEach
    void setUp() {
        webConfig = new WebConfig();
        // 手动注入私有依赖,比@InjectMocks可控性更高
        ReflectionTestUtils.setField(webConfig, "userDetailsService", userDetailsService);
        ReflectionTestUtils.setField(webConfig, "jwtRequestFilter", jwtRequestFilter);
        
        // 配置HttpSecurity链式调用的返回值,保证调用链不中断
        when(http.csrf()).thenReturn(csrfConfigurer);
        when(csrfConfigurer.disable()).thenReturn(http);
        when(http.addFilterBefore(eq(jwtRequestFilter), eq(UsernamePasswordAuthenticationFilter.class))).thenReturn(http);
        when(http.authorizeRequests()).thenReturn(interceptUrlRegistry);
        when(interceptUrlRegistry.antMatchers(
                "/v2/api-docs",
                "/swagger-resources/**",
                "/swagger-ui.html",
                "/webjars/**",
                "/swagger.json"
        )).thenReturn(interceptUrlRegistry);
        when(interceptUrlRegistry.permitAll()).thenReturn(interceptUrlRegistry);
    }

    @Test
    void testConfigureAuthentication() throws Exception {
        webConfig.configure(auth);
        // 验证认证管理器正确绑定了自定义UserDetailsService
        verify(auth, times(1)).userDetailsService(eq(userDetailsService));
    }

    @Test
    void testConfigureHttpSecurity() throws Exception {
        webConfig.configure(http);
        
        // 验证CSRF禁用逻辑生效
        verify(http, times(1)).csrf();
        verify(csrfConfigurer, times(1)).disable();
        
        // 验证JWT过滤器正确添加到过滤链
        verify(http, times(1)).addFilterBefore(eq(jwtRequestFilter), eq(UsernamePasswordAuthenticationFilter.class));
        
        // 验证Swagger相关接口白名单配置正确
        verify(http, times(1)).authorizeRequests();
        verify(interceptUrlRegistry, times(1)).antMatchers(
                "/v2/api-docs",
                "/swagger-resources/**",
                "/swagger-ui.html",
                "/webjars/**",
                "/swagger.json"
        );
        verify(interceptUrlRegistry, times(1)).permitAll();
    }
}

注意:ReflectionTestUtils是spring-test模块提供的工具类,正常Spring项目的测试依赖都会包含该类。

推荐集成测试方案

如果不需要做纯单元测试,用Spring Security官方测试模块做集成测试更简单,不需要手动Mock复杂的链式API,直接通过请求验证规则即可:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class WebConfigIntegrationTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    void testSwaggerEndpointIsPublic() throws Exception {
        mockMvc.perform(get("/v2/api-docs"))
                .andExpect(status().isOk());
    }

    @Test
    void testBusinessEndpointNeedAuth() throws Exception {
        mockMvc.perform(get("/api/user/info"))
                .andExpect(status().isUnauthorized());
    }
}

这种方案更贴近真实运行场景,验证结果可信度更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 23:06:03