Dmytro Chyzhykov's Blog

Yet another programmer.

JUnit / TestNG Testing Spring Security With Spring MVC Test Framework

Last year Rossen Stoyanchev announced the release of Spring MVC Test framework.

When I first tried to test my web application guarded by Spring Security with Spring MVC Test framework I noticed that Spring Security wasn’t enabled in the test context, but was in browser.

That “strange” behavior can be fixed by the following steps:

  1. authenticate user in Spring SecurityContext
  2. register Spring SecurityContext in a MockHttpSession instance
  3. use the MockHttpSession instance when you perform MockMvc requests

Here is what would it like in an example JUnit test suite

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/test/resources/spring/test-application-context.xml")
public class SpringSecurityTest {
    @Autowired
    private WebApplicationContext wac;

    @Autowired
    private FilterChainProxy springSecurityFilter;

    private MockMvc mockMvc;

    private final String SECURED_URI = "/";

    private final String LOGIN_PAGE_URL = "http://localhost/spring_security_login";

    @Before
    public void setUp() throws Exception {
        mockMvc = webAppContextSetup(wac)
                // Enable Spring Security
                .addFilters(springSecurityFilter)
                .alwaysDo(print()).build();
    }

    @Test
    public void itShouldDenyAnonymousAccess() throws Exception {
        mockMvc.perform(get(SECURED_URI))
                .andExpect(redirectedUrl(LOGIN_PAGE_URL));
    }

    @Test
    public void itShouldAllowAccessToSecuredPageForPermittedUser() throws Exception {
        Authentication authentication =
                new UsernamePasswordAuthenticationToken("user", "password");
        SecurityContext securityContext = SecurityContextHolder.getContext();
        securityContext.setAuthentication(authentication);

        MockHttpSession session = new MockHttpSession();
        session.setAttribute(
                HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
                securityContext);

        mockMvc.perform(get(SECURED_URI).session(session))
                .andExpect(status().isOk());
    }

}

TestNG version would look like the same but with some class level changes:

1
2
3
4
5
6
7
8
9
10
// imports omitted

@WebAppConfiguration
@ContextConfiguration("file:src/test/resources/spring/test-application-context.xml")
public class SpringSecurityTest extends AbstractTestNGSpringContextTests {
    // The same code here expect:
    //     SpringSecurityTest constructor which either implicitly or explicitly delegate to super();
    //     removed JUnit annotations
    //     added TestNG annotations
}