Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Spring mockMvc to test optional path variables

I have a method in Spring MVC with optional path variable. I am trying to test it for a scenario when optional path variable is not provided.

Snippet from Controller, resource URI to invoke-

@RequestMapping(value = "/some/uri/{foo}/{bar}", method = RequestMethod.PUT) public <T> ResponseEntity<T> someMethod(      @PathVariable("foo") String foo,       @PathVariable(value = "bar", required = false) String bar ) {     LOGGER.info("foo: {}, bar: {}", foo, bar); } 

Snippet from my test using MockMvc-

//inject context @Autowired private WebApplicationContext webApplicationContext;  protected MockMvc mockMvc;  @Before public void setup() {     //build mockMvc     mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); }  @Test public void someMethodTest() throws Exception {     //works as expected     mockMvc.perform(put("/some/uri/{foo}/{bar}", "foo", "bar"))             .andExpect(status().isOk()); //works      //following doesn't work      //pass null for optional     mockMvc.perform(put("/some/uri/{foo}/{bar}", "foo", null))             .andExpect(status().isOk()); //throws 404      //pass empty for optional     mockMvc.perform(put("/some/uri/{foo}/{bar}", "foo", ""))             .andExpect(status().isOk()); //throws 404      //remove optional from URI     mockMvc.perform(put("/some/uri/{foo}", "foo"))             .andExpect(status().isOk()); //throws 404 } 
like image 651
thisdotnull Avatar asked Aug 22 '17 19:08

thisdotnull


People also ask

How do you make a path variable optional in spring?

Another way to define an optional path variable that is available since Spring 3.2 is with a Map for @PathVariable parameters: @RequestMapping(value = {"/article", "/article/{id}"}) public Article getArticle(@PathVariable Map<String, String> pathVarsMap) { String articleId = pathVarsMap. get("id"); if (articleId !=

Can we make PATH variable optional spring boot?

The Spring Boot Optional Path Variable is used to handle the request urls that do not have path variable value. Optional Path variables in spring boot can be configured using the annotation @PathVariable.

Can we make PATH variable optional?

By design, in Spring MVC, it is not possible to have optional @PathVariable value. Still, we can use multiple path mappings to simulate this effect successfully.

What is the difference between PathVariable and RequestParam?

1) The @RequestParam is used to extract query parameters while @PathVariable is used to extract data right from the URI.


1 Answers

Using an array of @RequestMapping values like this ...

@RequestMapping(     value = {"/some/uri/{foo}", "/some/uri/{foo}/{bar}"},      method = RequestMethod.PUT) public ResponseEntity<String> someMethod(     @PathVariable("foo") String foo,      @PathVariable(value = "bar", required = false) String bar ) {     return new ResponseEntity<>(foo + " and " + (bar == null ? "<null>" : bar), HttpStatus.OK); } 

... will enable this test to pass:

@Test public void someMethodTest() throws Exception {     MvcResult mvcResult = mockMvc.perform(put("/some/uri/{foo}/{bar}", "foo", "bar"))             .andExpect(status().isOk()).andReturn();     Assert.assertEquals("foo and bar", mvcResult.getResponse().getContentAsString());      mvcResult = mockMvc.perform(put("/some/uri/{foo}/{bar}", "foo", null))             .andExpect(status().isOk()).andReturn();     Assert.assertEquals("foo and <null>", mvcResult.getResponse().getContentAsString());      mvcResult = mockMvc.perform(put("/some/uri/{foo}/{bar}", "foo", ""))             .andExpect(status().isOk()).andReturn();     Assert.assertEquals("foo and <null>", mvcResult.getResponse().getContentAsString());      mvcResult = mockMvc.perform(put("/some/uri/{foo}", "foo"))             .andExpect(status().isOk()).andReturn();     Assert.assertEquals("foo and <null>", mvcResult.getResponse().getContentAsString()); } 

That certainly seems to be the simplest solution and it is likely to be more friendly to tools such as Swagger since it make the mappings explicit.

However, you could also declare a wildcard mapping and then use a path matcher within your controller method to interpret the request URI. For example, this method ...

private final AntPathMatcher antPathMatcher = new AntPathMatcher();  @RequestMapping(value = "/some/uri/with/wildcards/**", method = RequestMethod.PUT) public ResponseEntity<String> someMethod(HttpServletRequest request) {     String matched = antPathMatcher.extractPathWithinPattern(             (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE), request.getPathInfo());     // ugly parsing code to read the path variables, allowing for the optionality of the second one     String foo = matched;     String bar = null;     String[] pathVariables = matched.split("/");     if (pathVariables.length > 1) {         foo = pathVariables[0];         bar = pathVariables[1];     }     return new ResponseEntity<>(foo + " and " + (bar == null ? "<null>" : bar), HttpStatus.OK); } 

... will enable this test to pass:

@Test public void someMethodTestWithWildcards() throws Exception {     MvcResult mvcResult = mockMvc.perform(put("/some/uri/with/wildcards/{foo}/{bar}", "foo", "bar"))             .andExpect(status().isOk()).andReturn();     Assert.assertEquals("foo and bar", mvcResult.getResponse().getContentAsString());      mvcResult = mockMvc.perform(put("/some/uri/with/wildcards/{foo}/{bar}", "foo", null))             .andExpect(status().isOk()).andReturn();     Assert.assertEquals("foo and <null>", mvcResult.getResponse().getContentAsString());      mvcResult = mockMvc.perform(put("/some/uri/with/wildcards/{foo}/{bar}", "foo", ""))             .andExpect(status().isOk()).andReturn();     Assert.assertEquals("foo and <null>", mvcResult.getResponse().getContentAsString());      mvcResult = mockMvc.perform(put("/some/uri/with/wildcards/{foo}", "foo"))             .andExpect(status().isOk()).andReturn();     Assert.assertEquals("foo and <null>", mvcResult.getResponse().getContentAsString()); } 
like image 132
glytching Avatar answered Sep 17 '22 21:09

glytching