I have POST method in a Spring boot rest controller as follows
@RequestMapping(value="/post/action/bookmark", method=RequestMethod.POST)
public @ResponseBody Map<String, String> bookmarkPost(
@RequestParam(value="actionType",required=true) String actionType,
@RequestParam(value="postId",required=true) String postId,
@CurrentUser User user) throws Exception{
return service.bookmarkPost(postId, actionType, user);
}
now if I test with missing parameter in Postman I get an 400 http response and a JSON body:
{
"timestamp": "2015-07-20",
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MissingServletRequestParameterException",
"message": "Required String parameter 'actionType' is not present",
"path": "/post/action/bookmark"
}
until now it's OK, but when I try to unit test I don't get the JSON response back
@Test
public void bookmarkMissingActionTypeParam() throws Exception{
// @formatter:off
mockMvc.perform(
post("/post/action/bookmark")
.accept(MediaType.APPLICATION_JSON)
.param("postId", "55ab8831036437e96e8250b6")
)
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.exception", containsString("MissingServletRequestParameterException")));
// @formatter:on
}
the test fails and produces
java.lang.IllegalArgumentException: json can not be null or empty
I did a .andDo(print()) and found that there is no body in the response
MockHttpServletResponse:
Status = 400
Error message = Required String parameter 'actionType' is not present
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store], Pragma=[no-cache], Expires=[1], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
why am I not getting the JSON response while unit testing my controller, but do receive it in manual testing using Postman or cUrl?
EDIT: I've added @WebIntegrationTest but got the same error:
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.http.MediaType;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = RestApplication.class)
@WebIntegrationTest
public class PostControllerTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(springSecurityFilterChain)
.build();
}
@Test
public void bookmarkMissingActionTypeParam() throws Exception{
// @formatter:off
mockMvc.perform(
post("/post/action/bookmark")
.accept(MediaType.APPLICATION_JSON)
.param("postId", "55ab8831036437e96e8250b6")
)
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.exception", containsString("MissingServletRequestParameterException")));
// @formatter:on
}
}
Originally, it should fix the error by
@ResponseBody
tag when defining your REST controller method. it will fix json error in the test class. But, as you are using spring boot, you will define the controller class with@RestController
and it should automatically take care of the error without defining@Controller
and@ResponseType
tags.This is because Spring Boot has auto-configured an exception handler
org.springframework.boot.autoconfigure.web.BasicErrorController
which is probably not present in your unit tests. A way to get it will be to use the Spring Boot testing support related annotations:More details are here
Update: You are absolutely right, the behavior is very different in UI vs in test, the error pages which respond to status codes are not correctly hooked up in a non-servlet test environment. Improving this behavior can be a good bug to open for Spring MVC and/or Spring Boot.
For now, I have a workaround which simulates the behavior of
BasicErrorController
the following way:What I am doing here is adding a
ControllerAdvice
which handles the Exception flow and delegates back to theBasicErrorController
. This would atleast make the behavior consistent for you.