5

I have a controller protected with HTTP Basic authentication.

I setup the app to use session cookies and it works.

However when I test the controller using MockMvc, a successful authentication does not give me any cookie.

Web configuration:

package world.pyb.spring.cookiesdemo;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("admin").password("argentina").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //@formatter:off
        http.httpBasic()
                .and().authorizeRequests().antMatchers(HttpMethod.GET, "/hello").authenticated()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
        //@formatter:on
    }
}

Simple controller:

package world.pyb.spring.cookiesdemo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String index() {
        return "Greetings from Spring Boot!";
    }

}

Simple controller test that doesn't give me the session cookie:

package world.pyb.spring.cookiesdemo;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void getHello() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/hello")
            .with(SecurityMockMvcRequestPostProcessors.httpBasic("admin", "argentina"))
    )
        // prints "Cookies = []"
        .andDo(MockMvcResultHandlers.print())
        .andExpect(cookie().exists("JSESSIONID"))
        .andExpect(status().is2xxSuccessful())
        .andExpect(content().string(equalTo("Greetings from Spring Boot!")));
    }

}

Related questions:

Some answers suggest not to use MockMvc but I'd like to keep using it if possible.

pyb
  • 4,813
  • 2
  • 27
  • 45
  • 3
    I just got a [Tumbleweed](https://stackoverflow.com/help/badges/63/tumbleweed) badge for having no vote, comment or answer on this question. If you're [from the future](https://xkcd.com/979/), know that I worked around it using [`TestRestTemplate.exchange`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html#exchange-java.lang.String-org.springframework.http.HttpMethod-org.springframework.http.HttpEntity-java.lang.Class-java.lang.Object...-) and reading the cookies from `ResponseEntity.getHeaders()`. – pyb Oct 24 '17 at 18:53

1 Answers1

5

As shown in the question's code, MockMvcResultMatcher includes support for cookies.

This will work fine, as long as the controller under test itself delivers the cookie. The problem here is that the cookie is delivered by Spring Security, which is a wrapper around your controller. MockMvc is testing your controller directly, and not testing your controller running in its real HTTP server, as would be required to test the security-layer cookies.

That's why TestRestTemplate, which invokes your controller in its full server context, delivers a more thorough test environment.

Notice however that as of Spring 5, the newer approach to running-server API testing is based on WebTestClient.

Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
  • `WebTestClient` provides a *fluid* syntax similar to that of `MockMvc`. – Brent Bradburn Apr 18 '18 at 21:51
  • 1
    I'm worried that WebTestClient brings with it a whole new world of hurt: [What is the difference between MockMvc and WebTestClient?](https://stackoverflow.com/a/49958609/86967) – Brent Bradburn Apr 21 '18 at 17:51
  • 1
    Looks like WebTestClient is meant for Webflux testing which projects that use SQL can't use :-/ – NeilS Jun 05 '18 at 14:53