Using Spring mockMvc to test optional path variables

谁说我不能喝 提交于 2020-01-13 08:08:37

问题


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
}

回答1:


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());
}



回答2:


This is late but I faced recently this situation and thought this post would help others.

In case of mocking endpoints with optional request param or path variable, you can specify it like this.

Say I have a method with params as m1(String param1, String param2) called from controller.

Where param 2 is an optional param for controller, so at runtime null would be passed if it is not passed.

How to mock:

Mockito.when(m1(Mockito.anyString(), Mockito.eq(null)).the return(<whatever you want to return>)

Use Mockito.eq(null) in your test to pass it as null for optional param.



来源:https://stackoverflow.com/questions/45825955/using-spring-mockmvc-to-test-optional-path-variables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!