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

Spring控制器测试中Mock WebSecurity依赖的实现方案

解决@WebMvcTest中因Spring Security依赖引发的NoSuchBeanDefinitionException问题

你碰到的这个问题很典型——@WebMvcTest作为Spring的切片测试注解,只会加载控制器、WebMvc相关配置以及Spring Security的基础配置,不会自动扫描和加载@Component、@Service、@Repository这类业务层Bean。但你的WebSecurityConfig依赖了MyUserDetailsService,而这个服务又依赖了UserObjectRepository,切片上下文里没有这些Bean,自然就抛出找不到Bean的异常了。

下面给你两种最常用的解决方案,都能在不启动完整上下文的情况下让测试正常运行:

方案一:用@MockBean快速Mock依赖服务

这是最简单直接的方式,我们只需要在测试类中给MyUserDetailsService添加@MockBean注解,Spring会自动创建这个服务的Mock实例并注册到切片上下文里,满足WebSecurityConfig的依赖需求。

因为我们用了@WithMockUser、@WithAnonymousUser这类Spring Security测试注解,测试过程中根本不会实际调用MyUserDetailsService的逻辑,所以这个Mock实例完全够用,不需要关心它的具体实现。

修改后的测试类代码如下:

@WebMvcTest(DemoController.class)
class DemoControllerTests {
    @Autowired
    private MockMvc mockMvc;

    // Mock掉WebSecurityConfig依赖的MyUserDetailsService
    @MockBean
    private MyUserDetailsService myUserDetailsService;

    @Test
    @WithAnonymousUser
    void shouldNotHaveAccessWhenAnonymous() throws Exception {
        this.mockMvc.perform(get("/"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser(username = "pascal", roles = "USER")
    void shouldHaveAccessWithUserRole() throws Exception {
        this.mockMvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string("Hello"));
    }

    // 顺便补充测试@PreAuthorize的admin接口权限控制
    @Test
    @WithMockUser(username = "admin", roles = "ADMIN")
    void shouldAccessAdminEndpointWithAdminRole() throws Exception {
        this.mockMvc.perform(get("/admin"))
                .andExpect(status().isOk())
                .andExpect(content().string("Admin"));
    }

    @Test
    @WithMockUser(username = "pascal", roles = "USER")
    void shouldDenyAccessToAdminEndpointForUser() throws Exception {
        this.mockMvc.perform(get("/admin"))
                .andExpect(status().isForbidden());
    }
}

这里不需要额外Mock UserObjectRepository,因为MyUserDetailsService已经是Mock实例了,它的方法默认不会触发对Repository的调用。

方案二:自定义测试专用的Spring Security配置

如果你想让测试环境更贴近真实的安全规则,但又不想加载整个业务层,可以自定义一个测试用的Security配置类,用@Import引入到测试中。

比如我们可以用InMemoryUserDetailsManager来模拟用户数据,代替真实的MyUserDetailsService:

@WebMvcTest(DemoController.class)
@Import(TestSecurityConfig.class)
class DemoControllerTests {
    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithAnonymousUser
    void shouldNotHaveAccessWhenAnonymous() throws Exception {
        this.mockMvc.perform(get("/"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @WithMockUser(username = "pascal", roles = "USER")
    void shouldHaveAccessWithUserRole() throws Exception {
        this.mockMvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string("Hello"));
    }

    // 测试admin接口权限
    @Test
    @WithMockUser(username = "admin", roles = "ADMIN")
    void shouldAccessAdminEndpointWithAdminRole() throws Exception {
        this.mockMvc.perform(get("/admin"))
                .andExpect(status().isOk())
                .andExpect(content().string("Admin"));
    }

    // 自定义测试用的Security配置
    @Configuration
    @EnableWebSecurity
    @EnableMethodSecurity // 开启方法级安全,对应控制器的@PreAuthorize
    static class TestSecurityConfig {
        @Bean
        public UserDetailsService userDetailsService() {
            // 创建测试用的用户和管理员
            UserDetails user = User.withUsername("pascal")
                    .password("{noop}test123") // {noop}表示不加密密码
                    .roles("USER")
                    .build();
            UserDetails admin = User.withUsername("admin")
                    .password("{noop}admin123")
                    .roles("ADMIN")
                    .build();
            return new InMemoryUserDetailsManager(user, admin);
        }

        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http.authorizeHttpRequests(auth -> auth
                    .anyRequest().authenticated() // 所有请求都需要认证
            )
            .formLogin(Customizer.withDefaults()); // 启用默认表单登录(测试用)
            return http.build();
        }
    }
}

这种方式适合你需要验证更复杂的安全规则,同时又不想依赖真实业务Bean的场景。

总结

  • 如果只是想快速验证控制器的权限逻辑,**方案一(@MockBean)**是最优选择,简洁高效;
  • 如果需要更贴近真实的安全配置测试,可以用方案二(自定义测试配置);
  • 始终记住@WebMvcTest的核心是切片测试,要尽量隔离控制器和下层业务依赖,专注于验证Web层的逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 06:38:17