What is the proper way to handle a bad parameter in a RESTful service? I have an endpoint right now that returns a 400 if the wrong data type is sent.
#ResponseBody
#RequestMapping(produces = "application/json", method = RequestMethod.GET, value="/v1/test")
public MyResponse getSomething(#RequestParam BigDecimal bd) {
MyResponse r = new MyResponse();
r.setBd(bd);
return r;
}
It would be really nice if the end user were to pass, say, a String instead of a BigDecimal, that the response would return a json with the response code, status, and whatever else I'd like it to contain, rather than just a 400. Is there a way to do this?
Update: My initial thought was to wrap every parameter and then check to see if it's the correct type in that wrapper class. This seems a bit silly. Isn't there a validator that I could just add to the classpath that would recognize something like this?
Also, there is a way to handle this quite easily with a Bean type that I could create on my own, but what about standard types like BigDecimal?
UPDATE-2: This update addresses answer that uses #ExceptionHandler.
TestController.java
import java.math.BigDecimal;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
#RequestMapping("/")
public class TestController {
//this is where correct input from user is passed, no binding errors
#RequestMapping(produces = "application/json", method = RequestMethod.GET, value="/v1/test")
public
#ResponseBody
MyResponse getSomething(#RequestParam BigDecimal bd) {
MyResponse r = new MyResponse();
r.setBd(bd);
return r;
}
//this will handle situation when you except number and user passess string (A123.00 for example)
#ExceptionHandler(ServletRequestBindingException.class)
public #ResponseBody MyErrorResponse handleMyException(Exception exception, HttpServletRequest request) {
MyErrorResponse r = new MyErrorResponse();
r.setEexception(exception);
return r;
}
}
TestUnitTest.java
public class TestUnitTest {
protected MockMvc mockMvc;
#Autowired
protected WebApplicationContext wac;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void test() throws Exception {
String url = "/v1/test?bd=a123.00";
log.info("Testing Endpoint::: " + url);
MvcResult result = mockMvc.perform(get(url))
.andExpect(status().isOk())
.andReturn();
log.info("RESPONSE::: " + result.getResponse().getContentAsString());
}
}
MyResponse.java
import java.math.BigDecimal;
public class MyResponse {
private BigDecimal bd;
public BigDecimal getBd() {
return bd;
}
public void setBd(BigDecimal bd) {
this.bd = bd;
}
}
MyErrorResponse.java
public class MyErrorResponse {
private Exception exception;
public Exception getException() {
return exception;
}
public void setEexception(Exception e) {
this.exception = e;
}
}
Use Spring #ExceptionHandler along with standard #RequestMapping annotation like this:
//this is where correct input from user is passed, no binding errors
#RequestMapping(produces = "application/json", method = RequestMethod.GET, value="/v1/test")
public
#ResponseBody
MyResponse getSomething(#RequestParam BigDecimal bd) {
MyResponse r = new MyResponse();
r.setBd(bd);
return r;
}
//this will handle situation when there's exception during binding, for example you except number and user passess string (A123.00 for example)
#ExceptionHandler(TypeMismatchException.class)
public
#ResponseBody
MyErrorResponse handleMyException(Exception exception, HttpServletRequest request) {
//....
}
TypeMismatchException is general exception thrown when trying to set a bean property. You can generalize code even more and catch every binding exception with few methods, for example:
#ExceptionHandler(TypeMismatchException.class)
public
#ResponseBody
String typeMismatchExpcetionHandler(Exception exception, HttpServletRequest request) {
return "type mismatch";
}
#ExceptionHandler(MissingServletRequestParameterException.class)
public
#ResponseBody
String missingParameterExceptionHandler(Exception exception, HttpServletRequest request) {
return "missing param";
}
#ExceptionHandler(Exception.class)
public
#ResponseBody
String generalExceptionHandler(Exception exception, HttpServletRequest request) {
return "general exception";
}
It's very flexible, allowing many parameters in signature and returned objects Annotation Type ExceptionHandler
With #ResponseBody you may return any object, that can be serialized into JSON. It's only required to have jackson library in your classpath but I assume that you already know this
Related
Have a custom error controller on Spring boot:
package com.example.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.boot.web.servlet.error.ErrorController;
import javax.servlet.http.HttpServletRequest;
#Controller
public class CustomErrorController implements ErrorController
{
#RequestMapping("/error")
public String handleError(HttpServletRequest request)
{
...
}
#Override
public String getErrorPath()
{
return "/error";
}
}
But, when compile says: getErrorPath() in ErrorController has been deprecated. Ok, i found information: use server.error.path property. Ok, add this in application.properties and delete the function, but now says: CustomErrorController is not abstract and does not override abstract method getErrorPath() in ErrorController, ¿need a deprecated function?.
How to made the custom error controller?, the ErrorController requires getErrorPath but it is deprecated, what is the correct alternative?.
Starting version 2.3.x, Spring boot has deprecated this method. Just return null as it is anyway going to be ignored. Do not use #Override annotation if you want to prevent future compilation error when the method is totally removed. You can also suppress the deprecation warning if you want, however, the warning (also the #Override annotation) is helpful to remind you to cleanup/fix your code when the method is removed.
#Controller
#RequestMapping("/error")
#SuppressWarnings("deprecation")
public class CustomErrorController implements ErrorController {
public String error() {
// handle error
// ..
}
public String getErrorPath() {
return null;
}
}
#Controller
public class CustomErrorController implements ErrorController {
#RequestMapping("/error")
public ModelAndView handleError(HttpServletResponse response) {
int status = response.getStatus();
if ( status == HttpStatus.NOT_FOUND.value()) {
System.out.println("Error with code " + status + " Happened!");
return new ModelAndView("error-404");
} else if (status == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
System.out.println("Error with code " + status + " Happened!");
return new ModelAndView("error-500");
}
System.out.println(status);
return new ModelAndView("error");
}
}
there is an #ControllerAdvice annotation
#ControllerAdvice
public class MyErrorController {
#ExceptionHandler(RuntimeException.class)
public String|ResponseEntity|AnyOtherType handler(final RuntimeException e) {
.. do handle ..
}
#ExceptionHandler({ Exception1.class, Exception2.class })
public String multipleHandler(final Exception e) {
}
}
To handle errors, There is no need to define a controller class
implementing an error controller.
To handle errors in your entire application instead of writing
#Controller
public class CustomErrorController implements ErrorController{
#RequestMapping("/error")
public String handleError(HttpServletRequest request)
{
...
}
}
use the below class
#ControllerAdvice
public class myExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<YourResponseClass> handleAllExceptions(Exception ex, WebRequest request) {
YourResponseClassexceptionResponse = new YourResponseClass(new Date(), ex.getMessage());// Its an example you can define a class with your own structure
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(CustomException.class)
public final ResponseEntity<YourResponseClass> handleAllExceptions(Exception ex, WebRequest request) {
YourResponseClass exceptionResponse = new YourResponseClass(new Date(), ex.getMessage()); // For reference
return new ResponseEntity<YourResponseClass>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(BadCredentialsException.class)
public final ResponseEntity<YourResponseClass> handleBadCredentialsException(BadCredentialsException ex, WebRequest request){
YourResponseClass exceptionResponse = new YourResponseClass(new Date(), ex.getMessage());// For refernece
return new ResponseEntity<>(exceptionResponse, HttpStatus.UNAUTHORIZED);
}
}
The class above annoted with #ControllerAdvice acts as custom exception handler and it handles all the expecptions thrown by ur application. In above code sample only three exceptions are showed for understanding. It can handle many exceptions
In your application if there's any exception thrown it will come to this class and send the response back. You can have a customized message and structure as per ur needs.
#Controller
public class AppErrorController implements ErrorController {
#RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if(status != null) {
int statusCode = Integer.valueOf(status.toString());
if (statusCode == HttpStatus.FORBIDDEN.value()) {
return "errorpages/error-403";
} else if (statusCode == HttpStatus.NOT_FOUND.value()) {
return "errorpages/error-404";
} else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "errorpages/error-500";
}
}
return "errorpages/error";
}
}
In my current project I frequently use bulk requests. I have simple BulkRequest<T> class:
import java.util.List;
import javax.validation.constraints.NotNull;
public class BulkRequest<T> {
#NotNull private List<T> requests;
public List<T> getRequests() { return this.requests; }
public void setRequests(List<T> requests) { this.requests = requests; }
}
It very simple to use with other beans, for example:
#RequestMapping(value = "/departments/{departmentId}/patterns",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> post(
final #PathVariable long departmentId,
final #Valid #RequestBody BulkRequest<AddPatternRequest> bulkRequest
) {
...
}
AddPatternRequest contains own rules for validation and represents only one request, which can be collected to bulk request:
import javax.validation.constraints.NotNull;
public class AddPatternRequest {
#NotNull private Long pattern;
public Long getPattern() { return this.pattern; }
public void setPattern(Long pattern) { this.pattern = pattern; }
}
But there's a problem. After the controller receives the bulk request, it validates only BulkRequest and checks if requests collection is null or not, but I need to validate nested request too.
How can I do it?
Add #Valid to the requests. Like this
#NotNull
#Valid
private List<T> requests;
Then nested objects are also validated
I have a HandlerInterceptorAdapter that intercepts all requests and performs user authorization checks. Very basically:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
User user = ... // get user
checkIfAuthorized(user); // throws AuthorizationException
return true;
}
I then have an #ExceptionHandler for that AuthorizationException.
#ExceptionHandler(value = AuthorizationException.class)
public ResponseEntity<String> handleNotAuthorized(AuthorizationException e) {
// TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
ResponseEntity<String> responseEntity = new ResponseEntity<>("You are not authorized to access that page.", HttpStatus.UNAUTHORIZED);
return responseEntity;
}
This is fine if the (unauthorized) request accepts text/plain (and can be easily changed for json).
How can I make different #ExceptionHandlers for specific Accept headers?
#RequestMapping has produces(). Is there something similar for #ExceptionHandler?
I know this comes late but I've been looking up a solution to this, came across this question and found what I think to be a better solution. You can return "forward:/error" in your #ExceptionHandler (returning a String) to forward the request to a
#RequestMapping("/error")
ErrorController {...}
and use
#RequestMapping(produces = "text/html")
ModelAndView errorPage() {...}
on one method of that ErrorController,
#RequestMapping(produces = "application/json") // or no 'produces' attribute for a default
MyJsonObject errorJson() {...} on another.
I think this is a pretty neat way to do it, it's probably already out there but I didn't find it when trying to look it up.
So basically the #ExceptionHandler is the same for all, but forwards to a controller that can do the usual stuff
I think of two approaches:
Manually
public ResponseEntity<String> handleNotAuthorized(AuthorizationException e, HttpServletRequest request) {
// TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
if (/*read header accept from request and build appropiate response*/) {}
ResponseEntity<String> responseEntity = new ResponseEntity<>("You are not authorized to access that page.", HttpStatus.UNAUTHORIZED);
return responseEntity;
Automatically
#ResponseBody
public SomeObject handleNotAuthorized(AuthorizationException e, HttpServletRequest request) {
// TODO Custom EXCEPTION HANDLER for json/jsp/xml/other types, based on content type
/* Construct someObject and let Spring MessageConverters transform it to JSON or XML. I don't remember what happens in case of HTML (it should go to a view)*/
return someObject;
Don't forget to set the Response's Status code.
Not exactly the same use case, but the same requirement. I solve it with a custom HttpMessageConverter implementation.
#RestController
#RequestMapping("/foo")
public class MyResource {
#GetMapping(path = "/{id}", produces = "application/json")
public ResponseEntity<MyDto> get (#PathVariable(ID) long id)
throws IOException {
throw new MyCustomException();
}
#GetMapping(path = "/{id}/export", produces = "application/zip")
public ResponseEntity<byte[]> export (#PathVariable(ID) long id)
throws IOException {
throw new MyCustomException();
}
}
...
#ControllerAdvice
public class MyCustomExceptionHandler {
#ResponseBody
#ExceptionHandler
#ResponseStatus(BAD_REQUEST)
public JsonAPIErrorDocument handleException (MyCustomException e) {
return ....;
}
}
...
public class JsonAPIErrorDocumentToByteArrayMessageConverter extends AbstractHttpMessageConverter {
public ErrorDocumentToByteArrayMessageConverter () {
super(new MediaType("application", "zip"), MediaType.ALL);
}
#Override
protected boolean supports (Class clazz) {
return JsonAPIErrorDocument.class == clazz;
}
#Override
protected Object readInternal (Class clazz, HttpInputMessage inputMessage)
throws IOException,
HttpMessageNotReadableException {
return new byte[0];
}
#Override
protected void writeInternal (Object t, HttpOutputMessage outputMessage)
throws IOException,
HttpMessageNotWritableException {
}
}
...
#EnableWebMvc
#Configuration
#ComponentScan({ "com.foo" })
public class ApplicationConfig implements WebMvcConfigurer {
...
#Override
public void configureMessageConverters (List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
converters.add(new ByteArrayHttpMessageConverter());
converters.add(new JsonAPIErrorDocumentToByteArrayMessageConverter());
}
...
}
Trying to build a RESTful web service using Spring MVC.
The controller should return specific Java types, but the response body must be a generic envelope. How can this be done?
The following sections of code are what I have so far:
Controller method:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyController {
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Response envelope:
public class Response<T> {
private String message;
private T responseBody;
}
ServiceDetails code:
public class ServiceDetails {
private String serviceName;
public ServiceDetails(String serviceName) {
this.serviceName = serviceName;
}
}
Intended final response to clients should appear as:
{
"message" : "Operation OK"
"responseBody" : {
"serviceName" : "MyService"
}
}
What you can do is having a MyRestController just wrapping the result in a Response like this:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyRestController {
#Autowired
private MyController myController;
#RequestMapping(value = "/details")
public #ResponseBody Response<ServiceDetails> getServiceDetails() {
return new Response(myController.getServiceDetails(),"Operation OK");
}
}
This solution keep your original MyController independant from your REST code. It seems you need to include Jackson in your classpath so that Spring will auto-magically serialize to JSON (see this for details)
EDIT
It seems you need something more generic... so here is a suggestion.
#Controller
#RequestMapping(value = "/mycontroller")
public class MyGenericRestController {
#Autowired
private MyController myController;
//this will match all "/myController/*"
#RequestMapping(value = "/{operation}")
public #ResponseBody Response getGenericOperation(String #PathVariable operation) {
Method operationToInvoke = findMethodWithRequestMapping(operation);
Object responseBody = null;
try{
responseBody = operationToInvoke.invoke(myController);
}catch(Exception e){
e.printStackTrace();
return new Response(null,"operation failed");
}
return new Response(responseBody ,"Operation OK");
}
private Method findMethodWithRequestMapping(String operation){
//TODO
//This method will use reflection to find a method annotated
//#RequestMapping(value=<operation>)
//in myController
return ...
}
}
And keep your original "myController" almost as it was:
#Controller
public class MyController {
//this method is not expected to be called directly by spring MVC
#RequestMapping(value = "/details")
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Major issue with this : the #RequestMapping in MyController need probably to be replaced by some custom annotation (and adapt findMethodWithRequestMapping to perform introspection on this custom annotation).
By default, Spring MVC uses org.springframework.http.converter.json.MappingJacksonHttpMessageConverter to serialize/deserialize JSON through Jackson.
I'm not sure if it's a great idea, but one way of solving your problem is to extend this class, and override the writeInternal method:
public class CustomMappingJacksonHttpMessageConverter extends MappingJacksonHttpMessageConverter {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
super.writeInternal(new Response(object, "Operation OK"), outputMessage);
}
}
If you're using XML configuration, you could enable the custom converter like this:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="path.to.CustomMappingJacksonHttpMessageConverter">
</mvc:message-converters>
</mvc:annotation-driven>
Try the below solution.
Create a separate class such ResponseEnvelop. It must implement ResponseBodyAdvice interface.
Annotate the above class with #ControllerAdvice
Autowire HttpServletRequest
Override methods according to your requirement. Take reference from below.
#Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (httpServletRequest.getRequestURI().startsWith("/api")) {
return true;
}
return false;
}
#Override
public Object beforeBodyWrite(
Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.OK.value()
|| ((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.CREATED.value()) {
return new EntityResponse(Constants.SUCCESS, body);
}
return body;
}
I have a simple annotated controller similar to this one:
#Controller
public class MyController {
#RequestMapping("/{id}.html")
public String doSomething(#PathVariable String id, Model model) {
// do something
return "view";
}
}
and I want to test it with an unit test like this:
public class MyControllerTest {
#Test
public void test() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test.html");
new AnnotationMethodHandlerAdapter()
.handle(request, new MockHttpServletResponse(), new MyController());
// assert something
}
}
The problem is that AnnotationMethodHandlerAdapter.handler() method throws an exception:
java.lang.IllegalStateException: Could not find #PathVariable [id] in #RequestMapping
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.resolvePathVariable(AnnotationMethodHandlerAdapter.java:642)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolvePathVariable(HandlerMethodInvoker.java:514)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:262)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:146)
I'd call what you're after an integration test based on the terminology in the Spring reference manual. How about doing something like:
import static org.springframework.test.web.ModelAndViewAssert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({/* include live config here
e.g. "file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml" */})
public class MyControllerIntegrationTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
private MyController controller;
#Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
// I could get the controller from the context here
controller = new MyController();
}
#Test
public void testDoSomething() throws Exception {
request.setRequestURI("/test.html");
final ModelAndView mav = handlerAdapter.handle(request, response,
controller);
assertViewName(mav, "view");
// assert something
}
}
For more information I've written a blog entry about integration testing Spring MVC annotations.
As of Spring 3.2, there is a proper way to test this, in an elegant and easy way. You will be able to do things like this:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("servlet-context.xml")
public class SampleTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void getFoo() throws Exception {
this.mockMvc.perform(get("/foo").accept("application/json"))
.andExpect(status().isOk())
.andExpect(content().mimeType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}
}
For further information, take a look at http://blog.springsource.org/2012/11/12/spring-framework-3-2-rc1-spring-mvc-test-framework/
A promising framework for testing Spring MVC
https://github.com/SpringSource/spring-test-mvc
The exception message refers to a "feed" variable, which isn't present in your sample code, it's likely being caused by something you haven't shown us.
Also, your test is testing Spring and your own code. Is this really what you want to do?
It's better to assume that Spring works (which it does), and just test your own class, i.e. call MyController.doSomething() directly. That's one benefit of the annotation approach - you don't need to use mock requests and responses, you just use domain POJOs.
I've found that you can manually insert a PathVariable mapping into the request object. This is distinctly non-ideal but appears to work. In your example, something like:
#Test
public void test() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test.html");
HashMap<String, String> pathvars = new HashMap<String, String>();
pathvars.put("id", "test");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars);
new AnnotationMethodHandlerAdapter().handle(request, new MockHttpServletResponse(), new MyController());
// assert something
}
I'd definitely be interested in finding a better option.
Provided you are using Spring 3.0.x.
Here I suggest a merger of Emil and scarba05 answers using spring-test not spring-test-mvc. Please skip this answer and refer to spring-test-mvc examples if you are using Spring 3.2.x or later
MyControllerWithParameter.java
#Controller
public class MyControllerWithParameter {
#RequestMapping("/testUrl/{pathVar}/some.html")
public String passOnePathVar(#PathVariable String pathVar, ModelMap model){
model.addAttribute("SomeModelAttribute",pathVar);
return "viewName";
}
}
MyControllerTest.java
import static org.springframework.test.web.ModelAndViewAssert.assertViewName;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.ModelAndViewAssert;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations =
{"file:src\\main\\webapp\\WEB-INF\\spring\\services\\servlet-context.xml"
})
public class MyControllerTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
#Before
public void setUp() throws Exception {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
this.handlerAdapter = applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
}
// Container beans
private MyControllerWithParameter myController;
private ApplicationContext applicationContext;
public ApplicationContext getApplicationContext() {
return applicationContext;
}
#Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public MyControllerWithParameter getMyController() {
return myController;
}
#Autowired
public void setMyController(MyControllerWithParameter myController) {
this.myController = myController;
}
#Test
public void test() throws Exception {
request.setRequestURI("/testUrl/Irrelavant_Value/some.html");
HashMap<String, String> pathvars = new HashMap<String, String>();
// Populate the pathVariable-value pair in a local map
pathvars.put("pathVar", "Path_Var_Value");
// Assign the local map to the request attribute concerned with the handler mapping
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars);
final ModelAndView modelAndView = this.handlerAdapter.handle(request, response, myController);
ModelAndViewAssert.assertAndReturnModelAttributeOfType(modelAndView, "SomeModelAttribute", String.class);
ModelAndViewAssert.assertModelAttributeValue(modelAndView, "SomeModelAttribute", "Path_Var_Value");
ModelAndViewAssert.assertViewName(modelAndView, "viewName");
}
}
I'm not sure my original answer is going to help with #PathVariable. I've just tried testing an #PathVariable and I get the following exception:
org.springframework.web.bind.annotation.support.HandlerMethodInvocationException: Failed to invoke handler method [public org.springframework.web.servlet.ModelAndView test.MyClass.myMethod(test.SomeType)]; nested exception is java.lang.IllegalStateException: Could not find #PathVariable [parameterName] in #RequestMapping
The reason is that the path variables in the request get parsed by an interceptor. The following approach works for me:
import static org.springframework.test.web.ModelAndViewAssert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml"})
public class MyControllerIntegrationTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
#Before
public void setUp() throws Exception {
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
}
ModelAndView handle(HttpServletRequest request, HttpServletResponse response)
throws Exception {
final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
final HandlerExecutionChain handler = handlerMapping.getHandler(request);
assertNotNull("No handler found for request, check you request mapping", handler);
final Object controller = handler.getHandler();
// if you want to override any injected attributes do it here
final HandlerInterceptor[] interceptors =
handlerMapping.getHandler(request).getInterceptors();
for (HandlerInterceptor interceptor : interceptors) {
final boolean carryOn = interceptor.preHandle(request, response, controller);
if (!carryOn) {
return null;
}
}
final ModelAndView mav = handlerAdapter.handle(request, response, controller);
return mav;
}
#Test
public void testDoSomething() throws Exception {
request.setRequestURI("/test.html");
request.setMethod("GET");
final ModelAndView mav = handle(request, response);
assertViewName(mav, "view");
// assert something else
}
I've add a new blog post on integration testing spring mvc annotations