days
-1
-5
hours
0
-5
minutes
-2
-1
seconds
0
-6
search
Spring Cleaning

Testing with Spring

NicolasFrankel
Cleaning image via Shutterstock

Have you neglected your Spring cleaning? Here Nicolas Frankel takes a closer look at the three ways of testing Spring MVC applications.

Previously, I showed you how to test Spring applications in general: by creating “modular” applications. Here, the term “modular” isn’t to be taken in the same sense as usual – that you can upgrade or replace a module with another. “Modular” means breaking the big monolithic Spring configuration into multiple configuration fragments, be they XML or JavaConfig based (and that certainly goes a long way toward making the application more modular in the traditional sense).

SEE ALSO: Spring configuration modularization for Integration Testing

Nowadays, most modern application are web applications, and in the Spring world, this means Spring MVC applications. There are a couple of ways to test such web applications, but they can be narrowed down to these three kinds.

Testing at code level

One can test at the code level, just as with any standard application. Basically, this means calling the desired method with known inputs and verifying the output. The testing code would look something like this:

PetController petController = new PetController();
Map model = new HashMap<>();
String forward = petController.initCreationForm(1, model);
// Assert the forward is the expected
// Assert the model has been updated

This works pretty well with standard applications, however there is a big drawback with Spring MVC applications. Testing in this way ignores important capabilities of the application: interceptors, internationalization, model instances done by other methods through the @ModelAttribute annotation, and so on. In essence, we are testing at a too low level to have any confidence in the result of the test.

Testing at HTML level

To fix this, it’s possible to go down one level and test at the HTML level. This approach is to load the page, locate the desired GUI components – e.g. the login field and the submit button, and interact with them in some way – fill the login field and click on the button, then finally assert components on the returned page – check the title is “Welcome”. One example of such testing tactics is the Selenium framework.

The main drawback of this solution is its fragility: GUI is the layer of the application that changes the most often and with the biggest changes; this means that the Return Over Investment is very low, if positive at all. Of course, there are a couple of mitigation techniques but they are just that, mitigations.

Testing at HTTP level

The final approach is to go even lower and test at the HTTP level. Instead of thinking about components, you think about requests and response i.e. you do not fill the login value in a field, you craft the request to add login=xxx to the query string. While URL are more stable than GUI components, parameter names are not. Besides, asserting the result still has to be done at the HTML level and suffer from the same drawback than the previous solution. HTTPUnit is a good example of a framework that can be used with this tactics.

As no existing solution meets our requirements, a new way must be found and this is exactly what Spring Test provides regarding Spring MVC. Spring Test allows you to test at the URL level, while asserting different things related to Spring – model, flash attributes, etc. with all Spring MVC features enabled – i18n, interceptors, etc.

The entry point into Spring MVC testing is the MockMvc class. It can be obtained through either of those snippets:

  • MockMvcBuilders.webAppContextSetup(WebApplicationContext).build() for “standard” applications to be deployed in application servers
  • MockMvcBuilders.standaloneSetup(Object…).build() for applications using the new Spring Boot module
1

Fig 1. Spring Test MVC overview

The MockMvc class itself is relatively small, it only provides a perform() method. It is however at a center of a complete class hierarchy, organized as in figure 1.

Request builder

A request builder is used to build Fake request objects. RequestBuilder is a simple interface offering a single buildRequest(ServletContext) returning a MockHttpServletRequest object. It implements the Builder pattern through the MockHttpServletRequestBuilder.

Instances of the latter can be obtained through the MockMvcRequestBuilders factory (notice the plural in the class name). There is one factory method for each HTTP method, named after the method, but in lower case: get(), put(), post(), etc. All methods are available in two different overloaded flavors:

Fig 2. Request Builder class hierarchy

Fig 2. Request Builder class hierarchy

  • One takes a single URI parameter
  • The other one accepts a String for an URI template and varargs Object for variables. The URI template (e.g. get(“/customer/{customerId}”)) will be resolved by the testing framework.

MockHttpServletRequestBuilder offers a lot of configuration features for the resulting Fake request in the form of a fluent API. There are two types of configuration methods, shown in figure 2.

  • Those related to the HTTP protocol itself:

Screen Shot 2015-02-27 at 16.30.38

  • Those related to the JavaEE API:

Screen Shot 2015-02-27 at 16.30.50

Here’s an example of using the fluent API:

MockServletContext servletContext = new MockServletContext(); MockHttpServletRequestBuilder builder = get("/customer/{id}", 1234L)
    .accept("text/html")
    .param("lang", "en")
    .secure(true);
MockHttpServletRequest request = builder.buildRequest(servletContext);

Request handler

Fig 3. Result Handler class hierarchy

Fig 3. Result Handler class hierarchy

A request handler is used to execute actions on a ResultActions instance. Spring provides a handler out-of-the-box to print an MvcResult’s complete details to the standard output.

Here’s an example of using it:

DefaultMockMvcBuilder mockMvcBuilder =
    webAppContextSetup((WebApplicationContext) applicationContext);
MockMvc mockMvc = mockMvcBuilder.build();
MockHttpServletRequestBuilder getCustomer = get("/customer/{id}", 1234L)
    .accept("text/html")
    .param("lang", "en")
    .secure(true);
mockMvc.perform(getCustomer).andDo(print());

Result matcher

Finally, a result matcher is a Spring MVC kind of assertion for a result. The MockMvcResultMatchers class is an entry point that returns either:

  • ResultMatcher instances:

Screen Shot 2015-02-27 at 16.31.00

  • Or objects dedicated to provide different types of ResultMatcher. Think of them as classes that offer a way to return tightly-grouped matchers:

Screen Shot 2015-02-27 at 16.31.16

With all of these, it’s quite easy to create useful and robust tests aimed at Spring MVC controllers. As an example, let’s create one for the Spring Clinic’s PetController. The processCreationForm(Pet, BindingResult, SessionStatus) method is a pretty good example, as its results are not only pure Spring but also inserts a row in the database: this is a pretty important output, so that has to be asserted too.

This leads to the following test:

import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.model.*;
import org.springframework.samples.petclinic.repository.*;
import org.springframework.test.context.*;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import o.s.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.testng.annotations.*;

import static o.s.test.web.servlet.request.MockMvcRequestBuilders.post;
import static o.s.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.testng.Assert.assertEquals;

@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration("classpath:spring/business-config.xml"),
    @ContextConfiguration("classpath:spring/mvc-core-config.xml")
})
@ActiveProfiles("jdbc")
public class PetControllerIT extends AbstractTestNGSpringContextTests {

    @Autowired
    private PetRepository petRepository;

    @Autowired
    private OwnerRepository ownerRepository;

    private Owner george;
    private PetType cat;
    private Pet johnDoe;

    @BeforeMethod // --- (A) ---
    protected void setUp() {
        george = new Owner();
        george.setId(1);
        cat = new PetType();
        cat.setId(1);
        johnDoe = new Pet();
        johnDoe.setName("John Doe");
        johnDoe.setBirthDate(new DateTime());
        johnDoe.setType(cat);
        george.addPet(johnDoe); // --- (A) ---
    }

    @Test
    public void should_create_new_pet() throws Exception {
        WebApplicationContext wac = (WebApplicationContext) applicationContext;
        MockMvc mvc = MockMvcBuilders.webAppContextSetup(wac).build();
        MockHttpServletRequestBuilder newPet =                          // --- (B) ---
            post("/owners/{ownerId}/pets/new", george.getId())
            .sessionAttr("pet", johnDoe);
        mvc.perform(newPet).andExpect(redirectedUrl("/owners/" + george.getId()))
            .andExpect(model().attributeExists("types"));               // --- (B) ---
        Pet actualPet = petRepository.findById(14);                     // --- (C) ---
        assertEquals(actualPet.getName(), johnDoe.getName());           // --- (D) ---
        assertEquals(actualPet.getType().getId(), johnDoe.getType().getId());
        assertEquals(actualPet.getOwner().getId(), johnDoe.getOwner().getId()); // --- (D) ---
    }
}

Section A

The Pet entity created from the controller needs to be set up, as well as its dependent objects, an Owner and a PetType. Note that the owner and the type just need to be set an id. To set the right id, just peek at the SQL script named populateDB.sql and notice types id 1 is ‘cat’ and owners id 1 is ‘george’. Though not strictly necessary, those values make the test more readable.

Section B

A new post request builder is created using methods seen in this article, with the required URL and the pet crafted in the setup set as a session attribute. Asserted outputs include the correct redirect URL and that the model contains pet types.

Line C

This line uses the autowired repository to load the Pet instance. The id of the Pet to be found is infered by adding 1 to the maximum value of the id of the pet inserted in the populateDB.sql script.

Section D

The Pet class does not override the equals(Object) method, thus each individual field has to be checked.Integration testing from the trenches

This example concludes this article that showed how to increase confidence in code using Spring MVC by testing at the controller level, using the right granularity assertions.

If you want to go further, there’s an entire book dedicated to Integration Testing called Integration Testing from the Trenches. Have a look at it, there’s a sample free chapter for you to assess the content!

Author
NicolasFrankel
Nicolas Frankel comes from a rather unorthodox background, since he holds MSc in both Architecture and Civil Engineering. Now a Sun Certified professional, he operates as a successful Java/Java EE architect with more than 10 years of experience in consulting for different clients. Based in France, he also practices (or practiced) as WebSphere Application Server administrator, certi ed Valtech trainer and part-time lecturer in different French universities, so as to broaden his understanding of software craftmanship. His interests in computer software are diversi ed, ranging from Rich Client Application, to Quality Processes via Open Source Software. When not tinkering with new products, or writing blog posts, he may be found practicing sport: squash, kickboxing and ski at the moment.

Leave a Reply

Be the First to Comment!

avatar
400
  Subscribe  
Notify of