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 extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); auth.inMemoryAuthentication() .passwordEncoder(encoder) .withUser("spring") .password(encoder.encode("secret")) .roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/private/**") .authenticated() .antMatchers("/public/**") .permitAll() .and() .httpBasic(); } }
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 mvccontroller and REST endpoints and also with secured methods.
As usual, all source code for the example here can be found over on GitHub.