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

使用@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 05:25:57