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

Entries in spring mvc (2)

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.

Sunday
May062012

Don't call names - Roo's Tag with a tag problem

Eureka, ish..

I was trying to find time to debug a problem that's been on my backlog for a while, and someone else ran into it. A reader over on the Manning Spring Roo in Action forum was getting hit with a 400 invalid request error when trying one of the samples from the book (the many-to-many example).

A while back, I ran into this problem - my m:m arrangement was between Courses and Tags, and the field name of the actual tag in the Tag entity was called 'tag'. When I did this, I got the same error. Someone hit it in the forums, we renamed it to 'name' and committed our code, and I forgot about it for a while.

My theory was that the type converters somehow were generated wrongly. Nope, I have a " target="_blank" class="offsite-link-inline">JUnit test to review that does it properly, so that's not the problem.

Actually, the problem is with the type conversion, but it's not the fault of the Roo code for type converters (to my knowledge so far). They are just doing what they are supposed to do.

The REAL Problem

First of all, my theory on the bad type converters was wrong. I proved it by writing a set of tests to make sure I a) understood the true workings of the generated type converters and b) that they worked. In the next edition of Roo in Action I think I need to devote some time to these. See the attached sample at the end to see my tests. Here is how they work (example - the tag):

Type Conversions

  • Convert an object -> a single line String representation as a default label for a given dropdown entry. (converterFactory.getTagToStringConverter())
  • Hydrate a Tag via a String representation of the key (converterFactory.getStringToTagConverter())
  • Hydrate a Tag via a primary key value (converterFactory.getIdToTagConverter())

All of these work just fine when tested individually, which they should.

The Tag to String converter is used in dropdowns for the many-to-many relationship, so that when the tag editing screen (which owns the relationship) allows a user to select one or more courses, it just shows the field values separated by spaces.

So, now what?

Spring MVC creates forms with a set of assumptions, including the fact that the form will be of a certain bean instance root. This is provided by specifying the modelAttribute in Spring MVC, but since Roo does this for us, we aren't in charge of that in a scaffolded UI.

Here is the create.jspx file for reference.

The scaffolded value is generated by create.tagx, which uses this code:

<form:form action="${form_url}" method="POST"
   modelAttribute="${modelAttribute}" enctype="${enctype}" 
   onsubmit="${jsCall}"> ...

The essential problem I think we're running into with the samples the way they are put together is:

  • The "bean context" / model attribute name of the form being submitted is expected to be 'tag'
  • The form is submitting fields in this form with names such as description, id, and also tag
  • Spring MVC is spitting nails on the fact that you have tag.tag in your request. It wants to convert the nested tag into another Tag object, by hydrating it (probably to support recursive relationships or something). Well, it takes your String for the tag label and tries to take it through idToTagConverter() which fails on a numeric parse.

How to fix For now, avoid using the entity name as a field name in a given entity. I need to do more extensive testing to see why this is happening. This is something that I should know by heart, I guess, but I've been wrestling with other areas of Java and Roo and haven't been as connected to the web forms as I used to be.

If anyone wants to take this on, I have posted the code sample in the original Manning thread (look on the second page) with the junit tests included. Just create a course, then create a tag with the 'tag' field of any alpha value.

[b]The sample test[/b]

I created a spring junit test to check my assumptions (I imported hamcrest-all for matching support to make the assertions readable quickly, the pom includes org.hamcrest:hamcrest-all:1.1).