15

I have a very simple RESTful Controller that consumes and produces JSON. I need to test this controller offline i.e. no server running, no database running. And I am going nuts for not being able to find a solution. My intial test cases will include:

  • Test REST URIs i.e. GET, POST, PUT, DELETE - I must be able to Assert data returned against data sent.
  • Assert will test JSON data

I have the following URIs:

  • /pcusers - Returns all users
  • /pcusers/{id} - Return a specific user
  • /pcusers/create/{pcuser} - Add user to db
  • /pcusers/update/{pcuser} - Update user
  • /pcusers/delete/{id} - Delete User

NOTE: This is NOT a typical MVC application. I DO NOT have Views. I have a pure REST controller that spits out JSON and consumes data in JSON format.

If someone could guide me in the right direction would be really appreciated.

Just to be clear how my code looks like:

@Controller
@RequestMapping("/pcusers")
public class PcUserController {
    protected static Logger logger = Logger.getLogger(PcUserController.class);

    @Resource(name = "pcUserService")
    private PcUserService pcUserService;

    @RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    public List<PcUser> readAll() {
        logger.debug("Delegating to service to return all PcUsers");
        return pcUserService.readAll();
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET, consumes = "application/json", produces = "application/json")
    @ResponseBody
    public PcUser read(@PathVariable String id) {
        logger.debug("Delegating to service to return PcUser " + id);
        return pcUserService.read(id);
    }

    @RequestMapping(value = "/create/{pcUser}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
    @ResponseBody
    public boolean create(@PathVariable PcUser pcUser) {
        logger.debug("Delegating to service to create new PcUser");
        return pcUserService.create(pcUser);
    }

    @RequestMapping(value = "/update/{pcUser}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
    @ResponseBody
    public boolean update(@PathVariable PcUser pcUser) {
        logger.debug("Delegating to service to update existing PcUser");
        return pcUserService.update(pcUser);
    }

    @RequestMapping(value = "/delete/{id}", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
    @ResponseBody
    public boolean delete(@PathVariable String id) {
        logger.debug("Delegating to service to delete existing PcUser");
        return pcUserService.delete(id);
    }
}

UPDATE (2/5/2012): After some research, I came across a Spring framework called spring-test-mvc. It looks very promising and I have managed to get a good start on this. But now I have a new problem. When I submit a GET request to "/pcusers/{id}", the control is passed to read method which is responsible for handling that mapping. Inside that method I have a pcUserService that does a read. Now, the problem is when I run this test, the pcUserService instance inside real controller is NULL; and therefore it ends up crashing as read cannot be called on a NULL object.

Here's PcUserControllerTest code:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/applicationContextTest.xml")
public class PcUserControllerTest {

    @Autowired
    PcUserService pcUserService;

    @Autowired
    PcUserController pcUserController;

    PcUser pcUser;

    @Before
    public void setUp() throws Exception {
        pcUser = new PcUser("John", "Li", "Weasley", "john", "john", new DateTime());

        pcUserService.create(pcUser);
    }

    public void tearDown() throws Exception {
        pcUserService.delete(pcUser.getId());
    }

    @Test
    public void shouldGetPcUser() throws Exception {
        standaloneSetup(pcUserController)
                .build()
                .perform(get("/pcusers/" + pcUser.getId()).accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }
}
Will Hartung
  • 107,347
  • 19
  • 121
  • 195
jsf
  • 2,511
  • 8
  • 28
  • 33
  • I have asked a new question here http://goo.gl/yywX9 which is related to the spring-test-mvc framework. – jsf Feb 06 '12 at 04:22
  • Thanks for updating your question with an answer. Seems very interesting as I was just looking for something like that. – Avi Jun 12 '13 at 13:16

1 Answers1

15

Here is one suggestion that should give you some ideas. I assume that you are familiar with the SpringJUnit4ClassRunner and the @ContextConfiguration. Start by creating an test application context that contains PcUserController and a mocked PcUserService. In the example PcUserControllerTest class below, Jackson is used to convert JSON messages and Mockito is used for mocking.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(/* Insert test application context here */)
public class PcUserControllerTest {

    MockHttpServletRequest requestMock;
    MockHttpServletResponse responseMock;
    AnnotationMethodHandlerAdapter handlerAdapter;
    ObjectMapper mapper;
    PcUser pcUser;

    @Autowired
    PcUserController pcUserController;

    @Autowired
    PcUserService pcUserServiceMock;

    @Before
    public void setUp() {
        requestMock = new MockHttpServletRequest();
        requestMock.setContentType(MediaType.APPLICATION_JSON_VALUE);
        requestMock.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);

        responseMock = new MockHttpServletResponse();

        handlerAdapter = new AnnotationMethodHandlerAdapter();
        HttpMessageConverter[] messageConverters = {new MappingJacksonHttpMessageConverter()};
        handlerAdapter.setMessageConverters(messageConverters);

        mapper = new ObjectMapper();
        pcUser = new PcUser(...);

        reset(pcUserServiceMock);
    }
}

Now, we have all the code needed to create the tests:

@Test
public void shouldGetUser() throws Exception {
    requestMock.setMethod("GET");
    requestMock.setRequestURI("/pcusers/1");

    when(pcUserServiceMock.read(1)).thenReturn(pcUser);

    handlerAdapter.handle(requestMock, responseMock, pcUserController);

    assertThat(responseMock.getStatus(), is(HttpStatus.SC_OK));
    PcUser actualPcUser = mapper.readValue(responseMock.getContentAsString(), PcUser.class);
    assertThat(actualPcUser, is(pcUser));
}


@Test
public void shouldCreateUser() throws Exception {
    requestMock.setMethod("POST");
    requestMock.setRequestURI("/pcusers/create/1");
    String jsonPcUser = mapper.writeValueAsString(pcUser);
    requestMock.setContent(jsonPcUser.getBytes());

    handlerAdapter.handle(requestMock, responseMock, pcUserController);

    verify(pcUserServiceMock).create(pcUser);
}
matsev
  • 26,664
  • 10
  • 102
  • 138
  • Your solution looks good but I believe the spring-test-mvc framework is easy to deal with. I have updated my original question to include the test code that I've written so far. Now, I have a new problem which is explained above in the update. – jsf Feb 05 '12 at 23:04
  • OK, I solved that problem. I did not do @Autowired on PcUserController. I have updated my test code. – jsf Feb 05 '12 at 23:13
  • I am testing your code up there and I get an exception which is documented here http://pastebin.com/yUtzqmW8 Could you shed some light? i don't understand this exception. I believe something inside AnnotationMethodHandlerAdapter is null. – jsf Feb 09 '12 at 00:25
  • The only way a NPE can occur on that particular line in my Spring version (3.1) is due to auto-unboxing a `Boolean` to `boolean`. Have you tried to attach the source code and debug it? In my test, I never reach this line (the thread exists on the previous return statement). Maybe you can find a clue by reading up on the [type level](http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html) mapping? – matsev Feb 09 '12 at 18:29
  • 1
    @jsinghfoss I have written a [blog post](http://www.jayway.com/2012/09/08/spring-controller-tests-2-0/) about a basic controller test using the spring-mvc-test framework (there is a 1.0.0.M1 release available), which removes most of the plumbing code. – matsev Sep 12 '12 at 19:53