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 {
        http.authorizeRequests()
            .antMatchers("/private/**")
            .hasRole("USER")
            .antMatchers("/public/**")
            .permitAll()
            .and()
            .httpBasic();
        return http.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 simply 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.