Using Spring security test to test a secured Spring MVC controller
<p>following the <a href="http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test" rel="nofollow">documentation</a>  about using <strong>Spring Security Test</strong> to  write tests for a spring MVC app that is wired behind <strong>Spring Security</strong>.</p>

<p>This is a vanilla spring-boot application employing a typical spring-security wiring. Here's the main Application.java</p>

<pre class="lang-java prettyprint-override"><code>@SpringBootApplication
public class Application {

    private static final Logger log = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}
</code></pre>

<p>Here's the wiring for the spring-security:</p>

<pre class="lang-java prettyprint-override"><code>@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("customUserDetailsService")
    UserDetailsService userDetailsService;

    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/","/sign_up").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            .csrf()
                .and()
            .exceptionHandling()
                .accessDeniedPage("/access_denied")
                .and()
            .logout()
                .permitAll();
    }
}
</code></pre>

<p>As you may see, all requests require to be authenticated except for those for "/" and "/sign_up". I have verified by deploying the application that the authentication schemes work just fine.</p>

<hr>

<p>Now comes the interesting part: writing spring mvc tests. The <a href="http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test" rel="nofollow">link</a> I provided gives some nice ways to write such tests where the <em>spring-security-test</em> framework allows inserting mock users/security-contexts. I've taken the approach of</p>

<ol>
<li>defining a mock UserDetails interface, and</li>
<li>using a SecurityContextFactory to create a SecurityContext, which</li>
<li>should be inserted into the application context during test launch.</li>
</ol>

<p>The code for 1. is as follows:</p>

<pre class="lang-java prettyprint-override"><code>@WithSecurityContext(factory=WithMockUserDetailsSecurityContextFactory.class)
public @interface WithMockUserDetails {
    String firstName() default "apil";
    String lastName() default "tamang";
    String password() default "test";
    long id() default 999;
    String email() default "apil@test.com";


}
</code></pre>

<p>The code for 2. is as follows:</p>

<pre class="lang-java prettyprint-override"><code>final class WithMockUserDetailsSecurityContextFactory
    implements WithSecurityContextFactory&lt;WithMockUserDetails&gt;{


    @Override
    public SecurityContext createSecurityContext(WithMockUserDetails mockUserDetails) {


        /*
         * Use an anonymous implementation for 'UserDetails' to return a
         * mock authentication object, which is then set to the SecurityContext
         * for the test runs.
         */
        UserDetails principal=new UserDetails() {
            @Override
            public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {

                //another anonmyous interface implementation.
                GrantedAuthority auth=new GrantedAuthority() {
                    @Override
                    public String getAuthority() {
                        return "ROLE_USER";
                    }
                };
                List&lt;GrantedAuthority&gt; authorities=new ArrayList&lt;&gt;();
                authorities.add(auth);
                return authorities;
            }

            @Override
            public String getPassword() {
                return mockUserDetails.password();
            }

            @Override
            public String getUsername() {
                return mockUserDetails.email();
            }

            @Override
            public boolean isAccountNonExpired() {
                return true;
            }

            @Override
            public boolean isAccountNonLocked() {
                return true;
            }

            @Override
            public boolean isCredentialsNonExpired() {
                return true;
            }

            @Override
            public boolean isEnabled() {
                return true;
            }
        };
        Authentication authentication=new
                UsernamePasswordAuthenticationToken(principal,principal.getPassword(),principal.getAuthorities());
        SecurityContext context= SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        return context;
    }
}
</code></pre>

<hr>

<p>Finally, here's the test class:</p>

<pre class="lang-java prettyprint-override"><code>@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebAppConfiguration
public class UserControllerTest {

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private Filter springSecurityFilterChain;

    private MockMvc mvc;

    @Before
    public void setup(){
        mvc= MockMvcBuilders
                .webAppContextSetup(context)                    
                .apply(springSecurity())
                .build();
    }

    @Test
    public void testRootIsOk() throws Exception {
        mvc.perform(get("/"))
                .andExpect(status().isOk());
    }

    @Test
    public void expectRedirectToLogin() throws Exception {
        mvc.perform(get("/testConnect"))
                .andExpect(redirectedUrl("http://localhost/login"));

    }

    @Test
    @WithMockUserDetails
    public void testAuthenticationOkay() throws Exception {
        mvc.perform(get("/testConnect"))
                .andExpect(content().string("good request."));
    }

}
</code></pre>

<p>Output of Test Run:</p>

<ol>
<li>Test 1 passes.</li>
<li>Test 2 passes.</li>
<li>Test 3 fails. Expected output  but got &lt;>. </li>
</ol>

<p>Very likely Test 3 failed because of the 'SecurityContext' never got appropriately populated. As per the documentation, it should have worked. Not sure what I missed. Any help is much appreciated.</p>