Showing posts with label mvc. Show all posts
Showing posts with label mvc. Show all posts
Testing Spring MVC Annotated JSON Controllers from JUnit
jramoyo
Update: While I still recommend this post for good reading, please refer to this post for a better approach at doing this.
My previous post explained how we can use AnnotationMethodHandlerAdapter to test annotations applied to Spring MVC Controllers. This post will attempt to explain how we can reuse the classes introduced in the previous post to test Spring Controllers that return a JSON response.
The @ResponseBody annotation allows Spring Controllers to define the contents of an HTTP response.
For this example, we assume that Spring MVC is configured to represent the contents of the HTTP response as JSON.
Let's say we have a controller which returns user information from a GET request:
In order to test the JSON response, we need to assign an appropriate HttpMessageConverter to the AnnotationMethodHandlerAdapter defined in AbstractControllerTest. The one we need is MappingJacksonHttpMessageConverter.
Extending AbstractControllerTest
We will create a new class called AbstractJsonControllerTest and extend from AbstractControllerTest. Here, we override the parent's constructor so that we can assign MappingJacksonHttpMessageConverter to the AnnotationMethodHandlerAdapter. We also add various convenience methods to help process JSON.
Example
Here is how we test MyJsonController:
Line 16 asserts that the JSON reponse is not null nor empty.
Line 18 converts the JSON response to a Map object.
Lines 19 to 21 asserts the correctness of the user information retrieved from the HTTP response.
The above snippets are available from Google Code.
My previous post explained how we can use AnnotationMethodHandlerAdapter to test annotations applied to Spring MVC Controllers. This post will attempt to explain how we can reuse the classes introduced in the previous post to test Spring Controllers that return a JSON response.
The @ResponseBody annotation allows Spring Controllers to define the contents of an HTTP response.
For this example, we assume that Spring MVC is configured to represent the contents of the HTTP response as JSON.
Let's say we have a controller which returns user information from a GET request:
@Controller
public class MyJsonController {
private final UserService userService = new UserService();
@RequestMapping(value = "/user/{username}", method = RequestMethod.GET)
public @ResponseBody User getUser(@PathVariable String username) {
return userService.getUser(username);
}
// Mocked for illustration purposes
private static class UserService {
public User getUser(String username) {
User user = new User(username);
user.setFirstName("Jan");
user.setLastName("Amoyo");
return user;
}
}
}
This controller returns a User object serialized within the body of the HTTP response (in our case, as JSON). Because it doesn't return an instance of ModelAndView, we cannot use the previously introduced AbstractControllerTest to test the output.In order to test the JSON response, we need to assign an appropriate HttpMessageConverter to the AnnotationMethodHandlerAdapter defined in AbstractControllerTest. The one we need is MappingJacksonHttpMessageConverter.
Extending AbstractControllerTest
We will create a new class called AbstractJsonControllerTest and extend from AbstractControllerTest. Here, we override the parent's constructor so that we can assign MappingJacksonHttpMessageConverter to the AnnotationMethodHandlerAdapter. We also add various convenience methods to help process JSON.
@Ignore("abstract test case")
public abstract class AbstractJsonControllerTest<T> extends AbstractControllerTest<T> {
private final ObjectMapper mapper;
public AbstractJsonControllerTest(T controller) {
super(controller);
mapper = new ObjectMapper();
MappingJacksonHttpMessageConverter jacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
MappingJacksonHttpMessageConverter[] messageConverters = { jacksonHttpMessageConverter };
getHandlerAdapter().setMessageConverters(messageConverters);
}
protected List<Map<String, Object>> convertJsonArrayHttpServletResponseToList(MockHttpServletResponse response) throws JsonParseException,
JsonMappingException, IOException {
return convertJsonArrayStringToList(response.getContentAsString());
}
protected List<Map<String, Object>> convertJsonArrayStringToList(String json) throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(json, new TypeReference<List<HashMap<String, Object>>>() {
});
}
protected Map<String, Object> convertJsonHttpServletResponseToMap(MockHttpServletResponse response) throws JsonParseException,
JsonMappingException, IOException {
return convertJsonStringToMap(response.getContentAsString());
}
protected Map<String, Object> convertJsonStringToMap(String json) throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(json, new TypeReference<HashMap<String, Object>>() {
});
}
protected String convertObjectToJsonString(Object object) throws JsonMappingException, JsonGenerationException, IOException {
return mapper.writeValueAsString(object);
}
}
Similar to AbstractControllerTest, we can use AbstractJsonControllerTest as the parent class to all test cases involving JSON Spring Controllers.Example
Here is how we test MyJsonController:
@RunWith(BlockJUnit4ClassRunner.class)
public class MyJsonControllerTest extends AbstractJsonControllerTest<MyJsonController> {
public MyJsonControllerTest() {
super(new MyJsonController());
}
@Test
public void testGetUser() throws Exception {
getRequest().setMethod("GET");
getRequest().setServletPath("/user/jramoyo");
ModelAndView modelAndView = invokeController();
assertNull(modelAndView);
String jsonString = getResponse().getContentAsString();
assertFalse(jsonString == null || jsonString.isEmpty());
Map<String, Object> jsonMap = convertJsonStringToMap(jsonString);
assertEquals("jramoyo", jsonMap.get("username"));
assertEquals("Jan", jsonMap.get("firstName"));
assertEquals("Amoyo", jsonMap.get("lastName"));
}
}
Line 13 asserts that ModelAndView is null as expected.Line 16 asserts that the JSON reponse is not null nor empty.
Line 18 converts the JSON response to a Map object.
Lines 19 to 21 asserts the correctness of the user information retrieved from the HTTP response.
The above snippets are available from Google Code.
Testing Spring MVC Annotated Controllers from JUnit
jramoyo
Let me begin by clarifying that this post is more about testing the correctness of annotations applied to Spring Controllers rather than unit testing the behavior of the controller methods.
Annotations on Spring Controllers are commonly tested manually, together with the views. Aside from the usual concerns that come with it, manual testing also involves significant overhead with
having to redeploy binaries and restarting the web server in-between fixes. While "hot-deploy" reduces a lot of these concerns, it still leaves some room for improvement.
AnnotationMethodHandlerAdapter
Starting with version 2.5, Spring MVC comes with a class called AnnotationMethodHandlerAdapter which can be used to programatically invoke an annotated Spring Controller. The handle() method from this class accepts an HttpServletRequest, an HttpServletResponse, and an Object representing the Controller to invoke. It returns an instance of ModelAndView.
We can use AnnotationMethodHandlerAdapter within a JUnit test case to simulate a web container handling a request to the Spring Controller.
Using AnnotationMethodHandlerAdapter together with Generics, we can create an abstract class which serves as a parent class to all Spring Controller unit test cases:
Example
Let's say we have a controller which returns a view named "index" and populates the current user's username as a model attribute:
Lines 11 to 13 sets-up the HTTP request by assigning the request method, request URL, and a fake Principal.
Line 15 invokes the request against the Spring Controller.
Lines 16-18 asserts the correctness of the returned ModelAndView.
The above snippets are available from Google Code.
On my next post, I will explain how to test Spring Controllers returning a JSON response by extending the above classes.
Annotations on Spring Controllers are commonly tested manually, together with the views. Aside from the usual concerns that come with it, manual testing also involves significant overhead with
having to redeploy binaries and restarting the web server in-between fixes. While "hot-deploy" reduces a lot of these concerns, it still leaves some room for improvement.
AnnotationMethodHandlerAdapter
Starting with version 2.5, Spring MVC comes with a class called AnnotationMethodHandlerAdapter which can be used to programatically invoke an annotated Spring Controller. The handle() method from this class accepts an HttpServletRequest, an HttpServletResponse, and an Object representing the Controller to invoke. It returns an instance of ModelAndView.
We can use AnnotationMethodHandlerAdapter within a JUnit test case to simulate a web container handling a request to the Spring Controller.
@Test
public void testController() throws Exception {
ModelAndView modelAndView = handlerAdapter.handle(request, response, myController);
assertEquals("index", modelAndView.getViewName());
}
We can mock HttpServletRequest and HttpServletResponse using popular mocking frameworks such Mockito or use the built-in MockHttpServletRequest and MockHttpServletResponse from the Spring Test module. This example uses the built-in mocked objects from Spring Test.Using AnnotationMethodHandlerAdapter together with Generics, we can create an abstract class which serves as a parent class to all Spring Controller unit test cases:
@Ignore("abstract test case")
public abstract class AbstractControllerTest<T> {
private final T controller;
private final AnnotationMethodHandlerAdapter handlerAdapter;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
public AbstractControllerTest(T controller) {
handlerAdapter = new AnnotationMethodHandlerAdapter();
this.controller = controller;
}
@Before
public void setUp() {
response = new MockHttpServletResponse();
request = new MockHttpServletRequest();
}
protected T getController() {
return controller;
}
protected AnnotationMethodHandlerAdapter getHandlerAdapter() {
return handlerAdapter;
}
protected MockHttpServletRequest getRequest() {
return request;
}
protected MockHttpServletResponse getResponse() {
return response;
}
protected ModelAndView invokeController() throws Exception {
return handlerAdapter.handle(request, response, controller);
}
}
As a parent class, it takes care of the boiler plate code involved in setting-up and invoking the controller.Example
Let's say we have a controller which returns a view named "index" and populates the current user's username as a model attribute:
@Controller
public class MyController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String getHomePage(Principal principal, Model model) {
if (principal != null) {
model.addAttribute("username", principal.getName());
}
return "index";
}
}
To test this, we create a concrete subclass of AbstractControllerTest and assign MyController as the generic type:
@RunWith(BlockJUnit4ClassRunner.class)
public class MyControllerTest
extends AbstractControllerTest<MyController> {
public MyControllerTest() {
super(new MyController());
}
@Test
public void testGetHomePage() throws Exception {
getRequest().setMethod("GET");
getRequest().setServletPath("/");
getRequest().setUserPrincipal(new HttpPrincipal("username", "realm"));
ModelAndView modelAndView = invokeController();
assertEquals("index", modelAndView.getViewName());
assertNotNull(modelAndView.getModelMap().get("username"));
assertEquals("username", modelAndView.getModelMap().get("username"));
}
}
Line 6 creates an instance of MyController and passes it as a parameter to the parent class' constructor.Lines 11 to 13 sets-up the HTTP request by assigning the request method, request URL, and a fake Principal.
Line 15 invokes the request against the Spring Controller.
Lines 16-18 asserts the correctness of the returned ModelAndView.
The above snippets are available from Google Code.
On my next post, I will explain how to test Spring Controllers returning a JSON response by extending the above classes.
3:14 PM
controller
,
junit
,
mvc
,
spring
Subscribe to:
Posts
(
Atom
)