15

I'm having some trouble testing a Spring Boot application with MockMvc.

I have the following test class:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {SpringConfiguration.class, SecurityConfiguration.class})
@IntegrationTest({"server.port=8080"})
@WebAppConfiguration
public class DemoTest {

@Autowired
private EmbeddedWebApplicationContext webApplicationContext;

private MockMvc mockMvc;

@Before
public void setUp() throws Exception {
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

@Test
public void testGetAccountUnauthenticated() throws Exception {
    mockMvc.perform(get("/accounts/1").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isUnauthorized());
}
}

This results in a HTTP 200 not a 401. I have component scanning and autoconfiguration enabled and spring security is configured in my SecuityConfiguration class as follows:

@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity // required for use of @AuthenticationPrincipal in MVC controllers.
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Override
public void configure(WebSecurity web) {
    web.debug(true);
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    //set up authentication.
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated();
   // set up form login
}
}

If I use a RestTemplate to access http://localhost:8080/accounts/1 then I get the expected behaviour (HTTP 401).

I have seen other examples (e.g. Spring Boot setup security for testing) that suggest that I autowire the FilterChainProxy and add the filter manually using the WebApplicationContext.addFilters(filterChainProxy) method. However, this actually fails for me (org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.web.FilterChainProxy] found).

I have two questions:

  1. Why does the injected WebApplicationContext not automatically use the SpringSecurity filters? Even if I could get the FilterChainProxy and add it manually, the JavaDoc for EmbeddedWebApplicationContext states

any {@link Servlet} or {@link Filter} beans defined in the context will be automatically registered with the embedded Servlet container

As a result I wouldn't expect to have to manually add the security filter chain since I (incorrectly?) expect this to "just work" due to the Auto Configuration magic in Spring Boot?

  1. Why is there no FilterChainProxy in the application context? Again, perhaps my expectations of the AutoConfiguration is incorrect - but I thought that this would be configured as part of the context configuration.

Thanks in advance for any advice.


Edits

  • The reason a FilterChainProxy doesn't get injected was because I has my configuration set to

    public void configure(WebSecurity web) { web.debug(true); }

This actually configures a org.springframework.security.web.debug.DebugFilter instead. The way I have now managed to get the Filter regardless of this debug setting is as follows:

@Resource(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
private Filter securityFilter;

If I add this to the MockMvcBuilder as follows:

MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilters(securityFilter)

then it does work as expected.

But, I don't understand why MockMVC would ignore the filters as this seems important for testing a request since anything could happen in a Filter that might impact the outcome of the test. Furthermore, it means that to test properly I'd need to lookup all Filters in the servlet context and establish their priority/url mapping and add them appropriately. This seems error prone and unnecessary.

Community
  • 1
  • 1
David
  • 7,652
  • 21
  • 60
  • 98
  • OK, so I haven't solved the problem. However, I believe that MockMVC simply ignores any configured filters on the EmbeddedWebApplicationContext. In my debugger if I evaluated the expression `webApplicationContext.getServletContext().getFilterRegistrations()` I can see a bunch of filters including the "springSecurityFilterChain". Whilst this doesn't explain to me why the FilterChainProxy can't be injected it also raises the question as to why MockMvc would simply ignore the filters configured in a Servlet Context. – David Nov 05 '14 at 17:06
  • 1
    Correct. That's why it's called Mock**MVC** (it's primarily for testing the ‘DispatcherServlet` and Spring MVC). But you have a complete app running on port 8080 in your test, so why not just hit that with a `RestTemplate` if you want end-to-end testing? – Dave Syer Nov 06 '14 at 07:02
  • 2
    The main reasons I'd opted to use MockMVC over a RestTemplate is I felt that the API was nicer to work with resulting in more succinct test code with the expectations - e.g. ".andExpect(status().isUnauthorized())". Secondly, I felt that it would be slightly easier to test making requests as different users rather than having to worry about passing special cookie values or request headers to ensure that I'm authenticated as a given user. Either way, I've decided to do exactly as you suggest and use the RestTemplate and not worry about MockMVC. – David Nov 06 '14 at 10:15

2 Answers2

8

I agree that MockMVC is perhaps more for testing SpringMVC and custom code in controllers, as commented by @dave-syer. So in cases when one wants to test spring MVC infrastructure with your custom controller code at the same time (correctness of controllers mapped to URLs; mapping and validation of input and output objects; standard controllers; your controllers) without leveraging the Servlet container part of the stack, MockMVC is there for you.

But MockMVC also does have methods to add filters, so it is designed with a possibility to engage Filters in the described type of testing. Sometimes filter may play functional role for code inside of a controller and that would be otherwise not testable with MockMVC.

With all that theory in mind I was trying to mimic Boot behaviour for my tests where filters would be set up in Spring Boot way and picked up by my tests to be used with MockVMC. Here is a snippet that I ended up using. It can surely be enhanced to mimic Boot behaviour in more precisely and extracted to some custom MockMVCBuilder.

@Autowired
private WebApplicationContext wac;

private MockMvc mockMvc;

@Before
public void setUp() {
    Collection<Filter> filterCollection = wac.getBeansOfType(Filter.class).values();
    Filter[] filters = filterCollection.toArray(new Filter[filterCollection.size()]);
    mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilters(filters).build();
}
ynovytskyy
  • 429
  • 5
  • 13
0

Have you tried this?

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;

...
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class AuthorizeTest {

    @Autowired
    private WebApplicationContext wac;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(wac)
                .apply(springSecurity())
                .build();
    }
    ...

}

In my case it is 403, not 401, but you get the idea.

WesternGun
  • 11,303
  • 6
  • 88
  • 157