使用@WebMvcTest测试REST控制器时Bean缺失问题求助
问题:使用
@WebMvcTest测试REST控制器时遇到JwtTokenProvider Bean缺失错误 我在使用@WebMvcTest注解对REST控制器做单元测试时遇到了问题,不想用@SpringBootTest启动完整上下文,希望仅通过@WebMvcTest解决。
测试类代码
@Import(SecurityConfig.class) @WebMvcTest(controllers = SomeController.class) class SomeControllerTest { private static final String BASE_URL = "/api/something"; @Autowired private MockMvc mockMvc; @MockBean private SomeService someService; @Test void shouldReturnNoContent() throws Exception { doNothing().when(pauseJobUseCase).pause(anyString()); mockMvc.perform(put(BASE_URL, anyString())).andExpect(status().isNoContent()); } }
安全配置代码
@Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { @Value("${security.username}") private String username; @Value("${security.password}") private String password; @Bean @Order(1) public SecurityFilterChain adminSecurityFilterChain(HttpSecurity http) throws Exception { return http .securityMatcher(AntPathRequestMatcher.antMatcher("/api/app/**")) .csrf(AbstractHttpConfigurer::disable) .cors(AbstractHttpConfigurer::disable) .sessionManagement( session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .httpBasic(withDefaults()) // Do not use in production environment! .build(); } @Bean @Order(2) public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .securityMatcher("/api/auth/**", "/api/admin/**") .csrf(AbstractHttpConfigurer::disable) .cors(AbstractHttpConfigurer::disable) .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) .sessionManagement( session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests( authorize -> authorize .requestMatchers("/api-docs/**", "/scheduler-saas.html", "/swagger-ui/**").permitAll() // will be handled on controller level (pre-authorized) .requestMatchers("/api/auth/**").permitAll() // "Open" API (requires correct credentials) .requestMatchers("/api/admin/**").permitAll() .anyRequest().authenticated() ) .build(); } @Bean @Order(3) public SecurityFilterChain jwtSecurityFilterChain(HttpSecurity http, JwtFilter jwtFilter) throws Exception { return http .securityMatcher("/api/job/**") .csrf(AbstractHttpConfigurer::disable) .cors(AbstractHttpConfigurer::disable) .headers(headers -> headers.frameOptions(FrameOptionsConfig::disable)) .sessionManagement( session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) { var userDetails = User.builder() .username(username) .password(passwordEncoder.encode(password)) .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(userDetails); } public record AccessViolationResponse(String message, String localizedMessage) { } }
错误信息
No qualifying bean of type 'com.hedza06.saasscheduler.security.jwt.JwtTokenProvider' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
使用技术栈
- Java 21
- Spring Boot 3.1.4
- Spring Security Core 6.1.4
解决思路
思路1:Mock缺失的JWT相关Bean
@WebMvcTest仅加载Web层核心Bean,而你的SecurityConfig中jwtSecurityFilterChain依赖JwtFilter,JwtFilter又依赖JwtTokenProvider,这些Bean不在Web层默认加载范围内。直接在测试类中用@MockBean补足缺失的Bean即可:
@Import(SecurityConfig.class) @WebMvcTest(controllers = SomeController.class) class SomeControllerTest { private static final String BASE_URL = "/api/something"; @Autowired private MockMvc mockMvc; @MockBean private SomeService someService; // 补足缺失的JWT相关MockBean @MockBean private JwtFilter jwtFilter; @MockBean private JwtTokenProvider jwtTokenProvider; @Test void shouldReturnNoContent() throws Exception { // 修正原代码:pauseJobUseCase应为someService doNothing().when(someService).pause(anyString()); // 修正原代码:URL参数不能用Mockito匹配器,传具体值 mockMvc.perform(put(BASE_URL, "test-id")) .andExpect(status().isNoContent()); } }
思路2:自定义测试专用的安全配置
如果你的SomeController对应的URL(/api/something)不在JWT过滤的/api/job/**范围内,可以直接在测试类中定义简化的安全配置,避免加载原配置中不需要的FilterChain:
@WebMvcTest(controllers = SomeController.class) class SomeControllerTest { private static final String BASE_URL = "/api/something"; @Autowired private MockMvc mockMvc; @MockBean private SomeService someService; // 测试专用安全配置,仅处理当前控制器的URL @TestConfiguration static class TestSecurityConfig { @Bean public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { return http .securityMatcher("/api/something/**") .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) // 测试时放开权限,按需调整 .build(); } } @Test void shouldReturnNoContent() throws Exception { doNothing().when(someService).pause(anyString()); mockMvc.perform(put(BASE_URL, "test-id")) .andExpect(status().isNoContent()); } }
这种方式无需导入原SecurityConfig,避免加载冗余的依赖Bean。
思路3:直接Mock SecurityFilterChain
如果测试仅关注控制器业务逻辑,无需验证安全规则,可以直接MockSecurityFilterChain,跳过整个安全配置的加载:
@WebMvcTest(controllers = SomeController.class) class SomeControllerTest { private static final String BASE_URL = "/api/something"; @Autowired private MockMvc mockMvc; @MockBean private SomeService someService; @MockBean private SecurityFilterChain securityFilterChain; @Test void shouldReturnNoContent() throws Exception { doNothing().when(someService).pause(anyString()); mockMvc.perform(put(BASE_URL, "test-id")) .andExpect(status().isNoContent()); } }
内容的提问来源于stack exchange,提问作者Heril Muratovic
相关产品推荐
相关产品推荐

