Spring MockMvc redirectedUrl with pattern

java, junit, mocking, spring

Solution

Since spring 4.0 you can use redirectedUrlPattern as pointed by Paulius Matulionis

As of spring 3.x this is not supported out of the box but you can easily add you custom result matcher

private static ResultMatcher redirectedUrlPattern(final String expectedUrlPattern) {
    return new ResultMatcher() {
        public void match(MvcResult result) {
            Pattern pattern = Pattern.compile("\\A" + expectedUrlPattern + "\\z");
            assertTrue(pattern.matcher(result.getResponse().getRedirectedUrl()).find());
        }
    };
}

And use it like build-in matcher

 mockMvc.perform(
    post("/person/save", new Object[0])
        .param("firstName", "JunitFN")
        .param("lastName", "JunitLN")
        .param("gender", "M")
        .param("dob", "11/02/1989")
 ).andExpect(
        redirectedUrlPattern("view/[0-9]+")
 );

Problem

I have a simple `PersonController` class that provides `save()` method to persist the object from http post request. ``` package org.rw.controller; import java.sql.Timestamp; import java.util.List; import org.rw.entity.Person; import org.rw.service.PersonService; import org.rw.spring.propertyeditor.TimestampPropertyEditor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping(value="/person") public class PersonController { private static final Logger logger = LoggerFactory.getLogger(PersonController.class); @Autowired private PersonService personService; @Autowired TimestampPropertyEditor timestampPropertyEditor; @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Timestamp.class, "dob", timestampPropertyEditor); } @RequestMapping(value="/save", method=RequestMethod.POST) public String save(Model model, Person person) { Long personId = personService.save(person); return "redirect:view/" + personId; } } ``` As the `save()` method returns as `return "redirect:view/" + personId;`. It will be diffrerent for every request. it may be like `"view/5"` or `"view/6"` depending on the id of the object that has been persisted. Then i have a simple class to test the above controller with spring mocking. ``` package org.rw.controller; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.Test; import org.rw.service.UserService; import org.rw.test.SpringControllerTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; public class PersonControllerTest extends SpringControllerTest { @Autowired private UserService userService; @Test public void add() throws Exception { mockMvc.perform(get("/person/add", new Object[0])).andExpect(status().isOk()); } @Test public void save() throws Exception { UserDetails userDetails = userService.findByUsername("anil"); Authentication authToken = new UsernamePasswordAuthenticationToken (userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authToken); mockMvc.perform( post("/person/save", new Object[0]) .param("firstName", "JunitFN") .param("lastName", "JunitLN") .param("gender", "M") .param("dob", "11/02/1989") ).andExpect( redirectedUrl("view") ); } } ``` now here i have a problem that `redirectedUrl("view")` is rejecting value `"view/5"`. I have tried `redirectedUrl("view*")` and `redirectedUrl("view/*")` but its not working. Edit : Here I have got a workaround as per below ``` MvcResult result = mockMvc.perform( post("/person/save", new Object[0]) .param("firstName", "JunitFN") .param("lastName", "JunitLN") .param("gender", "MALE") .param("dob", "11/02/1989") ).andExpect( //redirectedUrl("view") status().isMovedTemporarily() ).andReturn(); MockHttpServletResponse response = result.getResponse(); String location = response.getHeader("Location"); Pattern pattern = Pattern.compile("\\Aview/[0-9]+\\z"); assertTrue(pattern.matcher(location).find()); ``` but still i am looking for the proper way. update: I have posted the same issue on spring jira here :

Original source