1. Introduction
The ability to execute integration tests without the need for a standalone integration environment is a valuable feature for any software stack. The seamless integration of Spring Boot with Spring Security makes it simple to test components that interact with a security layer.
In this quick tutorial, we’ll explore using @MockMvcTest and @SpringBootTest to execute security-enabled integration tests.
2. Dependencies
Let’s first bring in the dependencies we’ll need for our example:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
The spring-boot-starter-web, spring-boot-starter-security, and spring-boot-starter-test starters provide us with access to Spring MVC, Spring Security, and the Spring Boot test utilities.
In addition, we’ll bring in spring-security-test in order to get access to the @WithMockUser annotation that we’ll be using.
3. Web Security Configuration
Our web security configuration will be straightforward. Only authenticated users will be able to access paths that match /private/** . Paths that match /public/** will be available for any user:
@Configuration
public class WebSecurityConfigurer {
@Bean
public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails user = User.withUsername("spring")
.password(passwordEncoder.encode("secret"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/private/**"))
.hasRole("USER"))
.authorizeHttpRequests(request -> request.requestMatchers(new AntPathRequestMatcher("/public/**"))
.permitAll())
.httpBasic(Customizer.withDefaults())
.build();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4. Method Security Configuration
In addition to the URL path-based security we defined in our WebSecurityConfigurer, we can configure method-based security by providing an additional configuration file:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfigurer
extends GlobalMethodSecurityConfiguration {
}
This configuration enables support for Spring Security’s pre/post annotations. Other attributes are available as well if additional support is required. For more information on Spring Method Security, take a look at our article on the topic.
5. Testing Controllers With @WebMvcTest
When using the @WebMvcTest annotation approach with Spring Security, MockMvc is automatically configured with the necessary filter chain required to test our security configuration.
Because MockMvc is configured for us, we’re able to use @WithMockUser for our tests without any additional configuration:
@RunWith(SpringRunner.class)
@WebMvcTest(SecuredController.class)
public class SecuredControllerWebMvcIntegrationTest {
@Autowired
private MockMvc mvc;
// ... other methods
@WithMockUser(value = "spring")
@Test
public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
mvc.perform(get("/private/hello").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Note that using @WebMvcTest will tell Spring Boot to instantiate only the web layer and not the entire context. Because of this, controller tests that use @WebMvcTest will run faster than with other approaches.
6. Testing Controllers With @SpringBootTest
When using @SpringBootTest annotation to test controllers with Spring Security, it’s necessary to explicitly configure the filter chain when setting up MockMvc.
Using the static springSecurity method provided by SecurityMockMvcConfigurer is the preferred way to do this:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SecuredControllerSpringBootIntegrationTest {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
// ... other methods
@WithMockUser("spring")
@Test
public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
mvc.perform(get("/private/hello").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
7. Testing Secured Methods With @SpringBootTest
@SpringBootTest doesn’t require any additional configuration to test secured methods. We can simply call the methods directly and use @WithMockUser as needed:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SecuredMethodSpringBootIntegrationTest {
@Autowired
private SecuredService service;
@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void givenUnauthenticated_whenCallService_thenThrowsException() {
service.sayHelloSecured();
}
@WithMockUser(username="spring")
@Test
public void givenAuthenticated_whenCallServiceWithSecured_thenOk() {
assertThat(service.sayHelloSecured()).isNotBlank();
}
}
8. Testing With @SpringBootTest and TestRestTemplate
TestRestTemplate is a convenient option when writing integration tests for secured REST endpoints.
We can autowire a template and set credentials before requesting secured endpoints:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class SecuredControllerRestTemplateIntegrationTest {
@Autowired
private TestRestTemplate template;
// ... other methods
@Test
public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
ResponseEntity<String> result = template.withBasicAuth("spring", "secret")
.getForEntity("/private/hello", String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
}
TestRestTemplate is flexible and offers many useful security-related options. For more details on TestRestTemplate, check out our article on the topic.
9. Conclusion
In this article, we looked at several ways of executing security-enabled integration tests.
We looked at how to work with MVC controller and REST endpoints and also with secured methods.
As usual, all source code for the example here can be found over on GitHub.