Chariot Training Classes

Training Courses

I run Chariot's training and mentoring services. We provide training in AngularJS, HTML5, Spring, Hibernate, Maven, Scala, and more.

Chariot Education Services

Technology

Chariot Emerging Tech

Learn about upcoming technologies and trends from my colleagues at Chariot Solutions.

Resources

Chariot Conferences

Podcasts

« Philly Emerging Tech April 2-3 2013 - Register today! | Main | todo-txt - very cool GTD task management from anything »
Wednesday
Dec192012

The future of Web MVC testing

Ok, ok, I'm excited. Right now I just was able to hack my way through a test with Spring 3.2 and the new test web context.

The method I ended up writing looked like this:

@Test
public void tryStartQuiz() throws Exception {
   this.mvc.perform(get("/engine/start/james"))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        .andExpect(jsonPath("$.quiz_id").exists());
}

How did I get here?

I upgraded to Spring 3.2, which has native support for loading Web contexts (well, not in a separate-but-parent/child way where you can mount the web context and business context separately, that's coming, but in one context).

Here is the top of my test class:


import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
   "file:src/main/webapp/WEB-INF/spring/webmvc-config.xml", 
   "classpath:/META-INF/spring/applicationContext.xml"})
public class EngineControllerTest {

    @Autowired
    WebApplicationContext context;

    MockMvc mvc;

    @Before
    public void setUp() {
        mvc = webAppContextSetup(this.context).build();
    }

    ...

Some analysis...

The MockMvc object, which I referred to as this.mvc in my test, allows me to mock up the web container request and record expectations. It is created in the @Before method, after Spring injects a test WebApplicationContext object, which is driven via the @WebAppConfiguration above.

The WebApplicationContext object is a web app engine in a test engine. It boots a Spring MVC platform, mocking the network engine so that you can submit requests with calls like mvc.perform(get(...) ... and chain assertions, using .andExpect statements to build conditions. It's like headless Spring MVC. This allows you to test by submitting URLs but without having to configure Jetty or Tomcat in a complex server bootstrapped integration test.

But where is it looking for my webapp?

As a nice touch, the default path for the web application is src/main/webapp. If you're not mavenized, or have a test version of your webapp you want to use, you can submit the path to it as the value of the annotation, like:

@WebApplicationContext("src/test/lightweightwebapp")

Assertions and matchers

There are several static imports that make life easier, as outlined in this great slide show from the MVC team (Rossen and friends):

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

I had to hack a bit with the commands and don't have the DSL figured out much so far. But it is nice to test through the request dispatcher rather than inject a controller directly. It's more natural and lets you really test the way production will handle your requests/responses.

What about more sophisticated checks?

The expect syntax doesn't really allow dicing and slicing of the response as easily as grabbing it in a variable. You can tell the mock MVC object to return the result, which contains the MockHttpServletResponse. In this example, I'm using FlexJson (from Roo's dependencies) to deserialize the JSON request into a HashMap of string keys and values, and assert that the data exists in the proper format.

@Test
public void tryStartQuiz() throws Exception {
	JSONDeserializer<HashMap<String, String>> deserializer
			= new JSONDeserializer<HashMap<String, String>>();

	MvcResult result = this.mvc.perform(get("/engine/start/james"))
			.andExpect(status().isOk())
			.andExpect(content().contentType(MediaType.APPLICATION_JSON))
			.andExpect(jsonPath("$.quiz_id").exists())
			.andReturn();

	Map<String, String> jsonPayload =
			deserializer.deserialize(
					result.getResponse().getContentAsString());

	assertThat(jsonPayload.containsKey("quiz_id"), is(true));
	assertThat(jsonPayload.get("nickname"), is("james"));

	System.err.println(jsonPayload);


}

Roo and Spring 3.2

One more thing - I came from Roo 1.2.2, and as I upgraded the spring.version property to Spring 3.2 (yes, it works just fine) so I had to add one dependency in order for this to work, test scoped:

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

Roo was just upgraded to version 1.2.3 this week (check it out here). I am hoping it uses Spring 3.2 out of the box.

This test will be checked into the next update of Quizzo-Angular-Roo - my current obsession which is using Spring Roo on the backend, and Angular.JS on the front-end, for a single-page JS quiz engine.

Where to go to view the code?

Today I've pushed this code (somewhat modified) along with working Jasmine tests, AND a working Maven test suite that includes automatic Jasmine testing every time I run mvn test. Check it out at quizzo-angular-roo on GitHub. I warn you, it will be primitive and so far not really doing much, but it will shape up over time.

PrintView Printer Friendly Version

EmailEmail Article to Friend

References (3)

References allow you to track sources for this article, as well as articles that were written in response to this article.

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>