I'm trying to use JUnit to test the controller endpoints of a project that is configured with Spring Security JWT, but it's throwing an exception when creating a bean with the name 'securityConfig' because it needs a bean of type 'jwtUtil' that doesn't can be found.
When I start the application and run some tests with Postman everything works perfectly, but with JUnit it's giving this problem.
Does anyone know how I can resolve it?
SecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private Environment env;
@Autowired
private JWTUtil jwtUtil;
public static final String[] PUBLIC_MATCHERS = {
"/h2-console/**"
};
public static final String[] PUBLIC_MATCHERS_GET = {
"/comics/**",
"/reviews/**",
"/comments/**"
};
public static final String[] PUBLIC_MATCHERS_POST = {
"/users/**"
};
@Override
protected void configure(HttpSecurity http) throws Exception {
if (Arrays.asList(env.getActiveProfiles()).contains("test")) {
http.headers().frameOptions().disable();
}
http.cors().and().csrf().disable();
http.authorizeRequests()
.antMatchers(HttpMethod.POST, PUBLIC_MATCHERS_POST).permitAll()
.antMatchers(HttpMethod.GET, PUBLIC_MATCHERS_GET).permitAll()
.antMatchers(PUBLIC_MATCHERS).permitAll()
.anyRequest().authenticated();
http.addFilter(new JWTAuthenticationFilter(authenticationManager(), jwtUtil));
http.addFilter(new JWTAuthorizationFilter(authenticationManager(), jwtUtil, userDetailsService));
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration().applyPermitDefaultValues();
configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "OPTIONS"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
JWTUtil
@Component
public class JWTUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() expiration))
.signWith(SignatureAlgorithm.HS512, secret.getBytes())
.compact();
}
public boolean validToken(String token) {
Claims claims = getClaims(token);
if (claims != null) {
String username = claims.getSubject();
Date expirationDate = claims.getExpiration();
Date now = new Date(System.currentTimeMillis());
if (username != null && expirationDate != null && now.before(expirationDate)) {
return true;
}
}
return false;
}
public String getUsername(String token) {
Claims claims = getClaims(token);
if (claims != null) {
return claims.getSubject();
}
return null;
}
private Claims getClaims(String token) {
try {
return Jwts.parser().setSigningKey(secret.getBytes()).parseClaimsJws(token).getBody();
} catch(Exception e) {
return null;
}
}
}
UserControllerTest
@ExtendWith(SpringExtension.class)
@ActiveProfiles("test")
@WebMvcTest(controllers = UserController.class)
@AutoConfigureMockMvc
public class UserControllerTest {
static String USER_API = "/users";
@Autowired
MockMvc mvc;
@MockBean
UserService userService;
ObjectMapper mapper;
@BeforeEach
public void setUp() {
mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
@Test
@DisplayName("Must save a user")
public void saveUserTest() throws Exception {
// Scenario
long id = 2l;
UserNewDTO newUser = UserNewDtoBuilder.aUserNewDTO().now();
UserDTO savedUser = UserDtoBuilder.aUserDTO().withId(id).now();
BDDMockito.given(userService.save(Mockito.any(UserNewDTO.class))).willReturn(savedUser);
String json = mapper.writeValueAsString(newUser);
// Execution
MockHttpServletRequestBuilder request = MockMvcRequestBuilders
.post(USER_API)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.content(json);
// Verification
mvc
.perform(request)
.andExpect( MockMvcResultMatchers.status().isCreated() )
.andExpect( MockMvcResultMatchers.header().string(HttpHeaders.LOCATION, Matchers.containsString("/users/" id)));
}
}
Exception
2022-09-14 10:13:33.423 WARN 2092 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig': Unsatisfied dependency expressed through field 'jwtUtil'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.gustavo.comicreviewapi.security.JWTUtil' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
2022-09-14 10:13:33.432 INFO 2092 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-09-14 10:13:33.745 ERROR 2092 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Field jwtUtil in com.gustavo.comicreviewapi.configs.SecurityConfig required a bean of type 'com.gustavo.comicreviewapi.security.JWTUtil' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.gustavo.comicreviewapi.security.JWTUtil' in your configuration.
2022-09-14 10:13:33.766 ERROR 2092 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@64c63c79] to prepare test instance [com.gustavo.comicreviewapi.resources.UserControllerTest@7302ff13]
java.lang.IllegalStateException: Failed to load ApplicationContext
CodePudding user response:
Since you are using @WebMvcTest
, Spring Boot will only consider the Spring MVC components as beans.
From the @WebMvcTest
javadoc, emphasis by me:
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not @Component, @Service or @Repository beans).
Since your JWTUtil
is a @Component
, you have to mock that bean as well:
@MockBean
JWTUtil jwtUtil;