Enterprise Java

Getting Started With Spring’s MVC Test Framework – Part 2

The first blog in this mini-series introduced the Spring MVC Test Framework and demonstrated its use in unit testing Spring MVC Controller classes as controllers rather then as POJOs. It’s now time to talk about using the framework for integration testing.

By ‘integration testing’ I mean loading the Spring context into the test environment so that the controller can work with its collaborators in ‘end to end’ tests.

Again, I’m going to write a test for the FacebookPostsController from my Spring Social Facebook project and the test will be, as you might expect, an integration test version of my  FacebookPostsControllerTest class. If you need to see the FacebookPostsController code or the original FacebookPostsControllerTestcode, take a look at my last blog. For a full run down on  the FacebookPostsController code see the Spring Social Facebook blog.

The first step in creating an integration test is to load the Spring context into your test environment. This is done by adding the following annotations to the FacebookPostsControllerTest class:

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @WebAppConfiguration
  3. @ContextConfiguration(“file-names”)
@RunWith(SpringJUnit4ClassRunner.class) 
@WebAppConfiguration 
@ContextConfiguration({ "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml", 
    "file:src/main/webapp/WEB-INF/spring/data.xml" }) 
public class FacebookPostsControllerTest {

There’s nothing new about @RunWith (SpringJUnit4ClassRunner.class) or @ContextConfiguration(“file-names”) as they’ve been around since Spring 2.5 and if you’re a Spring developer then you’ve probably used them in your integration tests before. The newcomer is @WebAppConfiguration.

These annotations work together to configure your test environment. @RunWith tells JUnit to run the test using the Spring JUnit class runner. @WebAppConfiguration tells SpringJUnit4ClassRunner that the ApplicationContext to load for the integration test should be a WebApplicationContext, whilst @ContextConfiguration is used to specify which XML file is loaded and where from.

In this case, I’m loading the project’s “servlet-context.xml” and “data.xml” files. The “servlet-context.xml” file contains all the standard bits and pieces you’d expect for a Spring web app such as <annotation-driven /> and the view resolvers, whilst “data.xml” contains the database config used by the Spring Social components of the application. The point to note here is that I’m purposely using the
pseudo-production config files as I want to run an end to end integration test accessing the file-system, database etc.

This is only sample code and you wouldn’t usually touch the production databases or other related resources in your integration tests. You would usually configure you app to access integration test databases and other resources. One way of solving this problem is to create an test XML config file; however, don’t, as I saw in one project, create a separate test XML file for every Maven module in your project; the reason being that when you make a change to your code, you end up changing a whole bunch of config files in order to get the integration tests working again, which is both boring and time consuming. A better approach is to have one version of your XML config and use Spring profiles to configure your application for different environments. If you do choose to use profiles, you’ll also need to add the  @ActiveProfiles(“profile-name”) annotation to the other three annotations listed above; however, that’s beyond the scope of this blog.

Assuming that you’re using autowiring and that you’ve got your <context:component-scan /> set up correctly, then the next step is to add the following instance variable to your test class:

  @Autowired 
  private WebApplicationContext wac;

This tells Spring to inject the WebApplicationContext it created earlier into your test. This is then used in the very simple one line setup() method:

  @Before 
  public void setUp() throws Exception { 

    mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); 
  }

Like the ‘standalone’/‘programmatic’ version of this test, the aim of the setup() method is to create a mockMvc instance and then to use it to perform the tests. The difference here is that it’s created simply by using the WebApplicationContext as an argument to the MockMvcBuilders.

Having sorted the setup() method out, the next thing to do is to write a test and I’m going to rewrite testShowPostsForUser_user_is_not_signed_in() from my last blog as an integration test. The surprise here is that the code is much simpler that the previous JUnit version:

  @Test 
  public void testShowPostsForUser_user_is_not_signed_in() throws Exception { 

    ResultActions resultActions = mockMvc.perform(get("/posts").accept(MediaType.ALL)); 
    resultActions.andExpect(status().isOk()); 
    resultActions.andExpect(view().name("signin")); 
  }

If you compare this code to the testShowPostsForUser_user_is_not_signed_in() code in my previous blog, you’ll see that it’s almost identical. The only difference is that there’s no need to setup any mock objects.

At this point I was going to demonstrate an integration test version of my testShowPostsForUser_user_is_signed_in test, but that turns out to be a little tricky. The reason for this is that to get hold of a list of their Facebook posts, the user has to sign in to their Facebook account and this means that there needs to be several sequential calls to the server before the necessary HttpServletRequest object is in the right state to facilitate a call to Facebook to retrieve a list of posts. This seemed a little too complex for sample code, and it’s something I wouldn’t like to do on a
real project.

Rather than viewing this complexity as a limitation of the Spring MVC Test Framework, I’d say that this highlights best practice, which is to ensure that so far as possible the calls to your server are independent and atomic.

Of course, I could use mock objects or create a dummy Facebook service but, again, that’s beyond the scope of this blog.

A good example of an independent, atomic server call is the REST call to testConfirmPurchases_selection_1_returns_a_hat(...) test for the OrderController class taken from my Spring MVC, Ajax and JSON Part 2 – The Server Side Code blog. This code, fully described in the Ajax blog, requests a purchase confirmation, which is returned as JSON.

The OrderController code that returns the JSON is listed below:

  /** 
   * Create an order form for user confirmation 
   */ 
  @RequestMapping(value = "/confirm", method = RequestMethod.POST) 
  public @ResponseBody 
  OrderForm confirmPurchases(@ModelAttribute("userSelections") UserSelections userSelections) { 

    logger.debug("Confirming purchases..."); 
    OrderForm orderForm = createOrderForm(userSelections.getSelection()); 
    return orderForm; 
  } 

  private OrderForm createOrderForm(List<String> selections) { 

    List<Item> items = findItemsInCatalogue(selections); 
    String purchaseId = getPurchaseId(); 

    OrderForm orderForm = new OrderForm(items, purchaseId); 
    return orderForm; 
  } 

  private List<Item> findItemsInCatalogue(List<String> selections) { 

    List<Item> items = new ArrayList<Item>(); 
    for (String selection : selections) { 
      Item item = catalogue.findItem(Integer.valueOf(selection)); 
      items.add(item); 
    } 
    return items; 
  } 

  private String getPurchaseId() { 
    return UUID.randomUUID().toString(); 
  }

Whilst the JSON it returns looks something like this:

{"items":[{"id":1,"description":"description","name":"name","price":1.00}, 
    {"id":2,"description":"description2","name":"name2","price":2.00}],
    "purchaseId":"aabf118e-abe9-4b59-88d2-0b897796c8c0"}

The code that tests the testConfirmPurchases_selection_1_returns_a_hat(...) is shown below in a verbose style.

  @Test 
  public void testConfirmPurchases_selection_1_returns_a_hat() throws Exception { 

    final String mediaType = "application/json;charset=UTF-8"; 

    MockHttpServletRequestBuilder postRequest = post("/confirm"); 
    postRequest = postRequest.param("selection", "1"); 

    ResultActions resultActions = mockMvc.perform(postRequest); 

    resultActions.andDo(print()); 
    resultActions.andExpect(content().contentType(mediaType)); 
    resultActions.andExpect(status().isOk()); 

    // See http://goessner.net/articles/JsonPath/ for more on JSONPath 
    ResultMatcher pathMatcher = jsonPath("$items[0].description").value("A nice hat"); 
    resultActions.andExpect(pathMatcher); 
  }

The code above isn’t how the Guys at Spring would prefer you to write it; however, in a verbose format is easier to discuss what’s going on. The structure of this method is similar to the  testShowPostsForUser_user_is_signed_in(...) method discussed in part 1. The first step is to create postRequest object of type MockHttpServletRequestBuilder using the static  MockMvcRequestBuilders.post(...) method. A "selection" parameter with a value of "1" is added resulting object.

ThepostRequest is then passed to the mockMvc.perform(...) method and a ResultActions object returned.

The ResultActions object is then verified using the andExpect(...) method to check both the HTTP status (ok = 200) and that the content type is "application/json;charset=UTF-8".

Additionally I’ve added anandDo(print()) method call to display the state of the HttpServletRequest and HttpServletResponse objects. The output from this call is shown below:

MockHttpServletRequest:
         HTTP Method = POST
         Request URI = /confirm
          Parameters = {selection=[1]}
             Headers = {}

             Handler:
                Type = com.captaindebug.store.OrderController
              Method = public com.captaindebug.store.beans.OrderForm com.captaindebug.store.OrderController.confirmPurchases(com.captaindebug.store.beans.UserSelections)

  Resolved Exception:
                Type = null

        ModelAndView:
           View name = null
                View = null
               Model = null

            FlashMap:

MockHttpServletResponse:
              Status = 200
       Error message = null
             Headers = {Content-Type=[application/json;charset=UTF-8]}
        Content type = application/json;charset=UTF-8
                Body = {"items":[{"id":1,"description":"A nice hat","name":"Hat","price":12.34}],"purchaseId":"d1d0eba6-51fa-415f-ac4e-8fa2eaeaaba9"}
       Forwarded URL = null
      Redirected URL = null
             Cookies = []

One final test uses the static MockMvcResultMatchers.jsonPath(...) to check that the JSON path of "$items[0].description" has a value of "A nice hat". In order to use the jsonPath(...) static method you must include the JSON Path module in your POM.xml to parse the JSON.

<dependency>
  <groupId>com.jayway.jsonpath</groupId>
  <artifactId>json-path</artifactId>
  <version>0.8.1</version>
  <scope>test</scope>
 </dependency>

JSonPath is a way of selectively extracting fields from JSon data. It’s based upon XML’s XPath idea.

Obviously there’s no need to write your tests is the verbose style I’ve used above. The code below shows the same code as the Guys at Spring designed it to be written:

  @Test 
  public void testConfirmPurchases_spring_style() throws Exception { 

    mockMvc.perform(post("/confirm").param("selection", "1")).andDo(print()) 
        .andExpect(content().contentType("application/json;charset=UTF-8")) 
        .andExpect(status().isOk()) 
        .andExpect(jsonPath("$items[0].description").value("A nice hat")); 
  }

So, that’s all there is to it. To recap, the idea is to add the appropriate annotations to your unit test so that Spring loads your XML config to create a WebApplicationContext. This is then injected into your test and passed to the Spring MVC Test framework as a parameter when creating the mockMvc. The tests are then written with the idea being to pass an appropriately constructed MockMvcRequestBuilders object to the mockMvc.perform(...) method, the return value of which is then asserted to either pass or fail your test.

The code for this blog is available on GitHub: https://github.com/roghughe/captaindebug/ in the Facebook and Ajax-JSON projects.
 

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button