9

Started studying Spring Test MVC Framework at the office today, it looks handy, but facing some serious trouble right off the bat. Spent a few hours googling, but couldn't find anything related to my issue.

Here's my very simple test class:

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebAppContext.class)
public class ControllerTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

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

    @Test
    public void processFetchErrands() throws Exception {
        mockMvc.perform(post("/errands.do?fetchErrands=true"))
               .andExpect(status().isOk())
               .andExpect(model().attribute("errandsModel", allOf(
                   hasProperty("errandsFetched", is(true)),
                   hasProperty("showReminder", is(false)))));
    }
}

The test reaches the following controller, but fails on first if clause thanks to not being authorized properly.

@RequestMapping(method = RequestMethod.POST, params="fetchErrands")
public String processHaeAsioinnit(HttpSession session, HttpServletRequest request, ModelMap modelMap,
                                  @ModelAttribute(ATTR_NAME_MODEL) @Valid ErrandsModel model,
                                  BindingResult result, JopoContext ctx) {
  if (request.isUserInRole(Authority.ERRANDS.getCode())) {
    return Page.NO_AUTHORITY.getCode();
  }

  [...]
}

How do I add a user role for the MockHttpServletRequest that gets created by MockMvcRequestBuilders.post(), so that I can get past the authority check on my controller?

I know MockHttpServletRequest has a method addUserRole(String role), but since MockMvcRequestBuilders.post() returns a MockHttpServletRequestBuilder, I never get my hands on the MockHttpServletRequest and thus cannot call that method.

Checking the Spring source, MockHttpServletRequestBuilder has no methods related to user roles, nor is the MockHttpServletRequest.addUserRole(String role) ever called in that class, so I have no idea how to tell it to add a user role into the request.

All I can think of is adding a custom filter to filter chain and calling a custom HttpServletRequestWrapper from there that provides an implementation of isUserInRole(), but that seems a little extreme for such a case. Surely the framework should offer something more practical?

t0mppa
  • 3,983
  • 5
  • 37
  • 48

5 Answers5

10

I think I found an easier way

@Test
@WithMockUser(username = "username", roles={"ADMIN"})
public void testGetMarkupAgent() throws Exception {

    mockMvc.perform(get("/myurl"))
            .andExpect([...]);
}

You might need the following maven entry

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <version>4.0.4.RELEASE</version>
        <scope>test</scope>
    </dependency>
Tk421
  • 6,196
  • 6
  • 38
  • 47
  • 1
    Depending on you security implementation, you may use 'authorities = {"ADMIN"}' instead of roles. – Razvan Jun 26 '18 at 11:48
2

Spring MVC Test has the principal() method that allows to mock the request credentials for cases like this. This is an example of a test where some mock credentials are set:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:spring/mvc-dispatcher-servlet.xml")
public class MobileGatewayControllerTest {

private MockMvc mockMvc;

@Autowired
private WebApplicationContext wac;  

@Autowired
private Principal principal;

@Autowired
private MockServletContext servletContext;

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


@Test
public void testExampleRequest() throws Exception {

    servletContext.declareRoles("ROLE_1");

    mockMvc.perform(get("/testjunit")
    .accept(MediaType.APPLICATION_JSON)
    .principal(principal))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(content().contentType("application/json"))
    .andExpect(jsonPath("$.[1]").value("test"));
}

}

and this is an example of how to create a mock principal:

@Configuration
public class SetupTestConfig {

@Bean
public Principal createMockPrincipal()  {
    Principal principal = Mockito.mock(Principal.class);
    Mockito.when(principal.getName()).thenReturn("admin");  
    return principal;
}

}

Angular University
  • 42,341
  • 15
  • 74
  • 81
  • This doesn't add the name into roles, nor does our authentication solution add the role into the principal's name. What point is there to a unit test that tests something that doesn't happen in production? – t0mppa Jan 03 '14 at 06:21
  • That's right I've edited the answer, one way of making request.isUserInRole() work in a spring MVC test is to inject the MockServletContext itself instead of the MockHttpServletRequest and declare some roles as above. – Angular University Jan 03 '14 at 10:38
  • Did you forget the injection part? Just adding an instance variable without assigning it won't fix this. – t0mppa Jan 03 '14 at 10:49
  • I've put autowiring for the instance variable servletContext in the test @Autowired private MockServletContext servletContext, you mean the call to servletContext.declareRoles() ? – Angular University Jan 03 '14 at 10:52
  • I've tested here locally and it works, request.isUserInRole("ROLE_1") returns true inside the controller. – Angular University Jan 03 '14 at 10:55
  • What kind of autowiring configuration did you use? Adding it as a bean to my config leaves the roles still empty inside the controller. – t0mppa Jan 03 '14 at 11:02
  • 1
    Ah, it fails if you define a configuration for `servletContext` autowiring, but magically works, if you omit the config. How logical, when everything else works the other way around. :( – t0mppa Jan 03 '14 at 13:53
1

You can inject a RequestPostProcessor to configure the MockHttpServletRequest before it's used in the request.

MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/")
                    .with(new RoleRequestPostProcessor("some role"));

class RoleRequestPostProcessor implements RequestPostProcessor {
    private final String role;

    public RoleRequestPostProcessor(final String role) {
        this.role = role;
    }

    @Override
    public MockHttpServletRequest postProcessRequest(final MockHttpServletRequest request) {
        request.addUserRole(role);
        return request;
    }
}
1

There is also an easy, alternative way of setting up specific user role for a single request. It might be handy if you want to perform only a single operation as an authorized user (like set up the test fixture), and then check if user with different role can perform certain operation:

ResultActions registerNewUserAsAdmin(String username, String password) throws Exception {
    final SignUpRequest signUpPayload = new SignUpRequest(username, password);

    final MockHttpServletRequestBuilder registerUserRequest = post(SIGN_UP_URL)
        .with(user("admin").roles("ADMIN"))
        .contentType(MediaType.APPLICATION_JSON_UTF8)
        .content(jsonMapper.writeValueAsString(signUpPayload));

    return mockMvc.perform(registerUserRequest);
}

See SecurityMockMvcRequestPostProcessors for more details.

qiubix
  • 1,302
  • 13
  • 22
0

IF you are using Authority instead of Roles, then grant the authority in the request like below.

mockMvc.perform(
            put(REQUEST_URL).param(PARM, VALUE)
                    .with(SecurityMockMvcRequestPostProcessors.user(USERNAME).authorities(new SimpleGrantedAuthority("ADMIN")))                        
                    .contentType(APPLICATION_FORM_URLENCODED)
    )
Shehan Simen
  • 1,046
  • 1
  • 17
  • 28