on software development and possibly other things

Testing Spring MVC Annotated JSON Controllers from JUnit

No comments
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:
@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&ltT&gt extends AbstractControllerTest&ltT&gt {
  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&ltMap&ltString, Object&gt&gt convertJsonArrayHttpServletResponseToList(MockHttpServletResponse response) throws JsonParseException,
      JsonMappingException, IOException {
    return convertJsonArrayStringToList(response.getContentAsString());
  }

  protected List&ltMap&ltString, Object&gt&gt convertJsonArrayStringToList(String json) throws JsonParseException, JsonMappingException, IOException {
    return mapper.readValue(json, new TypeReference&ltList&ltHashMap&ltString, Object&gt&gt&gt() {
    });
  }

  protected Map&ltString, Object&gt convertJsonHttpServletResponseToMap(MockHttpServletResponse response) throws JsonParseException,
      JsonMappingException, IOException {
    return convertJsonStringToMap(response.getContentAsString());
  }

  protected Map&ltString, Object&gt convertJsonStringToMap(String json) throws JsonParseException, JsonMappingException, IOException {
    return mapper.readValue(json, new TypeReference&ltHashMap&ltString, Object&gt&gt() {
    });
  }

  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.

No comments :

Post a Comment