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

如何用Mockito实现Spring Security配置类单元测试的100%覆盖率?

问题描述

我有一个基于Spring Security的配置类SecurityConfiguration,它继承自WebSecurityConfigurerAdapter,代码如下:

@AllArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private AuthenticationManager authenticationManager;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(arc -> arc
                        .antMatchers("/**").authenticated()
                )
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf(AbstractHttpConfigurer::disable)
                .oauth2ResourceServer(rsc ->
                        rsc.opaqueToken(
                                (OAuth2ResourceServerConfigurer<HttpSecurity>.OpaqueTokenConfigurer configurer) ->
                                        configurer.authenticationManager(authenticationManager)
                        )
                );
    }
}

我尝试用Mockito编写单元测试,但运行后代码未完全覆盖,测试代码如下:

@ExtendWith(MockitoExtension.class)
class SecurityConfigurationTest {

    @Mock private HttpSecurity httpSecurity;
    @Mock private SessionManagementConfigurer<HttpSecurity> sessionManagementConfigurer;

    @InjectMocks
    private SecurityConfiguration securityConfiguration;

    @Test
    void testConfigure() throws Exception {
        when(httpSecurity.authorizeRequests(any(Customizer.class))).thenReturn(httpSecurity);
        when(httpSecurity.sessionManagement()).thenReturn(sessionManagementConfigurer);
        when(sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .thenReturn(sessionManagementConfigurer);
        when(sessionManagementConfigurer.and()).thenReturn(httpSecurity);
        when(httpSecurity.csrf(any(Customizer.class))).thenReturn(httpSecurity);
        when(httpSecurity.oauth2ResourceServer(any(Customizer.class))).thenReturn(httpSecurity);

        securityConfiguration.configure(httpSecurity);

        verify(httpSecurity, times(1)).authorizeRequests(any(Customizer.class));
        verify(httpSecurity, times(1)).sessionManagement();
        verify(sessionManagementConfigurer, times(1))
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        verify(sessionManagementConfigurer, times(1)).and();
        verify(httpSecurity, times(1)).csrf(any(Customizer.class));
        verify(httpSecurity, times(1)).oauth2ResourceServer(any(Customizer.class));
    }
}
实现100%代码覆盖率的方法

当前测试仅验证了顶层方法调用,未覆盖lambda表达式内部的逻辑。需要捕获这些lambda并执行,同时验证内部的配置操作:

  1. 新增必要的Mock对象:为配置链中涉及的AuthorizationManagerRequestMatcherRegistry、CsrfConfigurer、OAuth2ResourceServerConfigurer、OpaqueTokenConfigurer创建Mock,用于捕获lambda内部的调用。
  2. 捕获并执行lambda:使用ArgumentCaptor捕获每个Customizer实例,手动执行其customize方法,触发内部配置逻辑。
  3. 验证lambda内部的具体操作:确保每个配置细节(如路径匹配、认证要求、csrf禁用、token配置等)都被验证。

修改后的完整测试代码:

@ExtendWith(MockitoExtension.class)
class SecurityConfigurationTest {

    @Mock
    private HttpSecurity httpSecurity;
    @Mock
    private SessionManagementConfigurer<HttpSecurity> sessionManagementConfigurer;
    @Mock
    private AuthorizationManagerRequestMatcherRegistry authorizationRegistry;
    @Mock
    private CsrfConfigurer<HttpSecurity> csrfConfigurer;
    @Mock
    private OAuth2ResourceServerConfigurer<HttpSecurity> oauth2ResourceServerConfigurer;
    @Mock
    private OAuth2ResourceServerConfigurer<HttpSecurity>.OpaqueTokenConfigurer opaqueTokenConfigurer;
    @Mock
    private AuthenticationManager authenticationManager;

    @InjectMocks
    private SecurityConfiguration securityConfiguration;

    @Test
    void testConfigure() throws Exception {
        // 捕获authorizeRequests的Customizer并模拟返回
        ArgumentCaptor<Customizer<AuthorizationManagerRequestMatcherRegistry>> authCaptor =
                ArgumentCaptor.forClass(Customizer.class);
        when(httpSecurity.authorizeRequests(authCaptor.capture())).thenReturn(httpSecurity);
        when(authorizationRegistry.antMatchers("/**")).thenReturn(authorizationRegistry);
        when(authorizationRegistry.authenticated()).thenReturn(authorizationRegistry);

        // 模拟sessionManagement相关调用链
        when(httpSecurity.sessionManagement()).thenReturn(sessionManagementConfigurer);
        when(sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .thenReturn(sessionManagementConfigurer);
        when(sessionManagementConfigurer.and()).thenReturn(httpSecurity);

        // 捕获csrf的Customizer并模拟返回
        ArgumentCaptor<Customizer<CsrfConfigurer<HttpSecurity>>> csrfCaptor =
                ArgumentCaptor.forClass(Customizer.class);
        when(httpSecurity.csrf(csrfCaptor.capture())).thenReturn(httpSecurity);

        // 捕获oauth2ResourceServer的Customizer并模拟嵌套调用
        ArgumentCaptor<Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>>> oauthCaptor =
                ArgumentCaptor.forClass(Customizer.class);
        when(httpSecurity.oauth2ResourceServer(oauthCaptor.capture())).thenReturn(httpSecurity);
        
        ArgumentCaptor<Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>.OpaqueTokenConfigurer>> opaqueTokenCaptor =
                ArgumentCaptor.forClass(Customizer.class);
        when(oauth2ResourceServerConfigurer.opaqueToken(opaqueTokenCaptor.capture())).thenReturn(oauth2ResourceServerConfigurer);
        when(opaqueTokenConfigurer.authenticationManager(authenticationManager)).thenReturn(opaqueTokenConfigurer);

        // 执行配置方法
        securityConfiguration.configure(httpSecurity);

        // 手动执行捕获的lambda,触发内部配置逻辑
        authCaptor.getValue().customize(authorizationRegistry);
        csrfCaptor.getValue().customize(csrfConfigurer);
        oauthCaptor.getValue().customize(oauth2ResourceServerConfigurer);
        opaqueTokenCaptor.getValue().customize(opaqueTokenConfigurer);

        // 验证顶层方法调用
        verify(httpSecurity, times(1)).authorizeRequests(any(Customizer.class));
        verify(httpSecurity, times(1)).sessionManagement();
        verify(sessionManagementConfigurer, times(1))
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        verify(sessionManagementConfigurer, times(1)).and();
        verify(httpSecurity, times(1)).csrf(any(Customizer.class));
        verify(httpSecurity, times(1)).oauth2ResourceServer(any(Customizer.class));

        // 验证lambda内部的具体配置细节
        verify(authorizationRegistry, times(1)).antMatchers("/**");
        verify(authorizationRegistry, times(1)).authenticated();
        verify(csrfConfigurer, times(1)).disable();
        verify(oauth2ResourceServerConfigurer, times(1)).opaqueToken(any(Customizer.class));
        verify(opaqueTokenConfigurer, times(1)).authenticationManager(authenticationManager);
    }
}

关键说明

  • 通过ArgumentCaptor捕获每个配置环节的Customizer实例,手动调用customize方法确保lambda内部代码被执行。
  • 新增了配置链中所有需要的Mock对象,覆盖了所有层级的调用。
  • 不仅验证顶层方法调用,还验证了每个具体的配置规则,确保所有代码分支都被覆盖。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 02:45:33