Richard’s Weblog

August 14, 2008

Test-Driving a SimpleFormController with Spring MVC

Filed under: Spring Framework,Web development — Richard @ 2:38 pm
Tags: , ,

In this article, I will show how one can develop “test-first” using Spring-webMVC and spring-test. I therefore assume you know the basics of spring-mvc. My example will be around some Person management system. First, assume we have a Person object :

public class Person {
    private Long id;
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Long getId() {
        return id;
    }

    private void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

And a service interface allowing basic CRUD operations for a person :

public interface PersonService {
    public List read(Person p);
    public void save(Person p);
    public void delete(Person p);
}

We want a web interface that allow the user to save a new Person or to modify an existing person. For this I’ll use a SimpleFormController. It will be responsible for presenting the form, calling the person’s service save(Person), and forwarding the user to a success page (when the person has correctly been saved).

This controller will be configured in a context file like this :

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean name="/editPerson" class="myProject.web.EditPersonController">
        <property name="commandClass" value="myProject.Person"/>
        <property name="formView" value="personForm"/>
        <property name="successView" value="personSaved"/>
    </bean>

</beans>

To have our test being able to load a context, we’ll use the jUnit’s @RunWith annotation, and to make our test load our specific context we’ll use the Spring’s @ContextConfiguration annotation. For more details on Spring’s test API, see the chapter 13 of the Spring framework documentation.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"/myProject/EditPersonControllerTest-context.xml"})
public class EditPersonControllerTest {
    <strong>@Autowired</strong>
    <strong>EditPersonController controller;</strong>

    /**
     * Tests the creation of a new Person.
     **/
    @Test
    public void testCreate() {

    }
}

The @Autowired annotation will do “by-type” bean injection. This means that since our context contains a single bean of type myProject.web.EditPersonController, that bean (the one we named “/editPerson”) will be injected in field “controller” at runtime, before the test methods are called. But for now, the test just doesn’t compile because we haven’t created the EditPersonController class. Let’s create this controller :

import org.springframework.web.servlet.mvc.SimpleFormController;

public class EditPersonController extends SimpleFormController {
}

It does nothing for the moment, except allowing the test to compile.
To test this newly created controller, we need a HttpServletRequest and a HttpServletResponse object. These objects will be passed to the controller’s handleRequest method. Our test is, of course, not using a servlet engine that is able to create these objects for us, so we have to mock them. Here comes the use of the Spring’s MockHttpServletRequest and MockHttpServletResponse classes. With these objects, we can easily simulate a HTTP request that will be forwarded to our controller. We can then verify if a “GET” HTTP call will make our controller send the person form :

/**
 * Tests the creation of a new Person.
 **/
@Test
public void testCreate() {
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setMethod("GET");
    ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
    assertEquals(controller.getFormView(), mv.getViewName());
}

The ModelAndView object is used to determine which view the controller chose to send to the user.
Instead of doing :
assertEquals(controller.getFormView(), mv.getViewName());
We could have done :
import static org.springframework.test.web.ModelAndViewAssert.*;
[...]
assertViewName(mv, controller.getFormView());
[...]

Now the user is provided a form with two input fields. One for the “firstname” and one for the “lastname”. He fills those fields and submit the form. The controller is then called with the “fistname” and “lastname” posted by the user. It must normally save the person and return the “successView” which we named “personSaved” (see the XML context defined previously). Let’s now add this behavior to our test :

import static org.springframework.test.web.ModelAndViewAssert.*;

...

/**
 * Tests the creation of a new Person.
 **/
@Test
public void testCreate() {
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setMethod("GET");
    ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
    assertEquals(controller.getFormView(), mv.getViewName());

    request = new MockHttpServletRequest();
    request.setMethod("POST");
    request.setParameter("firstName", "Richard");
    request.setParameter("lastname", "Barabé");
    mv = controller.handleRequest(request, new MockHttpServletResponse());
    assertViewName(mv, controller.getSuccessView());
}

So far we’ve tested that while creating a new Person, the user first asks the Person form (with an HTTP GET call), then submits that form (with an HTTP POST call that details person’s information) and is redirected to a success page when finished. We can say we tested the navigation part of the “create new Person” use case.

But, did the controller really saved the Person ? In other words : Did the controller called the PersonService’s save(Person p) method ? To test this, I will use EasyMock to mock a PersonService, tell EasyMock I want this PersonService’s save(Person p) method to be called once with the correct person, and then I will pass that PersonService mock to the controller. Once done, my test will know if the controller really called the PersonService adequately. Here is the code of the test :

import static org.easymock.EasyMock.*;

...

/**
 * Tests the creation of a new Person.
 **/
@Test
public void testCreate() {
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setMethod("GET");
    ModelAndView mv = controller.handleRequest(request, new MockHttpServletResponse());
    assertEquals("personForm", mv.getViewName());

    request = new MockHttpServletRequest();
    request.setMethod("POST");
    request.setParameter("firstName", "Richard");
    request.setParameter("lastname", "Barabé");
    // Create the service's imitation
    PersonService serviceMock = createMock(PersonService.class);
    controller.setPersonService(serviceMock);
    // The save method should be called with "Richard Barabé"
    serviceMock.save(new Person("Richard", "Barabé"));
    // The save method should be called only once
    expectLastCall().once();
    // Finished telling easymock how my service should be used.
    replay(serviceMock);

    mv = controller.handleRequest(request, new MockHttpServletResponse());
    assertViewName(mv, "personSaved");

    // Verify that the service have been used as we expect
    // (method save called once with "Richard Barabé")
    verify(serviceMock);
}

For this to compile, I need to add a PersonService field and its getter/setter in the controller :

import org.springframework.web.servlet.mvc.SimpleFormController;
import myProject.PersonService;
public class EditPersonController extends SimpleFormController {
    private PersonService service;

    public void setPersonService(PersonService service) {
        this.service = service
    }

    public PersonService getPersonService() {
        return this.service;
    }
}

Now the test asserts the service has been called correctly. As we run it, we can see it fails with the following message :

java.lang.AssertionError:
  Expectation failure on verify:
    save(myProject.Person@1f2cea2): expected: 1, actual: 0
	at org.easymock.internal.MocksControl.verify(MocksControl.java:82)
	at org.easymock.EasyMock.verify(EasyMock.java:1410)
        ...

This just means our controller didn’t call the service’s save method as expected. Let’s make our controller do it’s job by adding the expected call :

import org.springframework.web.servlet.mvc.SimpleFormController;
import myProject.PersonService;
public class EditPersonController extends SimpleFormController {
    private PersonService service;

    protected doSubmitAction(Object o) {
        this.service.save((Person) o)
    }

    public void setPersonService(PersonService service) {
        this.service = service
    }

    public PersonService getPersonService() {
        return this.service;
    }
}

Running the test now should succeed. But, unfortunately, the bar is red again. For time consideration, I’ll leave aside the investigation and go right to the solution : we did not override the equals and hashcode methods of the Person class. Our controller that received the form submission did create a new Person, setting its first and last name. It then passed this new object to the doFormSubmitAction(Object) that called our mock passing the new person. But we told EasyMock we expected our service to be called with another Person object. Default implementation of the equals method in object makes the following statement false :
(new Person(“Richard”,”Barabé”)).equals(new Person(“Richard”,”Barabé”))
So let’s implement those hashCode and equals methods (I use Eclipse to generate this “boiler plate” code) :

public class Person {
    private Long id;
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Long getId() {
        return id;
    }

    private void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public int hashCode() {
    	final int prime = 31;
    	int result = 1;
    	result = prime * result
    	        + ((firstName == null) ? 0 : firstName.hashCode());
    	result = prime * result
    	        + ((lastName == null) ? 0 : lastName.hashCode());
    	return result;
    }

    @Override
    public boolean equals(Object obj) {
    	if (this == obj)
    	    return true;
    	if (obj == null)
    	    return false;
    	if (getClass() != obj.getClass())
    	    return false;
    	final Person other = (Person) obj;
    	if (firstName == null) {
    	    if (other.firstName != null)
    	        return false;
    	} else if (!firstName.equals(other.firstName))
    	    return false;
    	if (lastName == null) {
    	    if (other.lastName != null)
    	        return false;
    	} else if (!lastName.equals(other.lastName))
    	    return false;
        return true;
    }
}

Now the code behaves correctly when we have two persons with the same fistName and lastName. And that green bar is back again.

There we are, we have successfully developped and tested our controller. This article is to be continued, and next time we’ll go over the Validator and View implementation.

Advertisements

7 Comments »

  1. In August 2008 you should be using annotations instead of SimpleFormController which is so 2007.

    Comment by me — August 21, 2008 @ 2:11 pm | Reply

  2. Nice post, very useful. I’m waiting for the next one on validators :)

    Comment by David S. — September 11, 2008 @ 7:36 pm | Reply

  3. super ;) Continue comme ça, t’es un génie!!

    Comment by Caroline — February 24, 2009 @ 4:36 am | Reply

  4. My friend on Orkut shared this link with me and I’m not dissapointed that I came to your blog.

    Comment by How to Get Six Pack Fast — April 15, 2009 @ 10:58 am | Reply

  5. Good day! This is kind of off topic but I need some help from an established blog.
    Is it difficult to set up your own blog? I’m not very techincal but I can figure things out pretty quick. I’m thinking about setting up my own but I’m not sure where to start. Do you have any points or suggestions? Appreciate it

    Comment by quirky jewellery — April 15, 2013 @ 2:56 pm | Reply

  6. Thanks , I have just been searching for info approximately this topic for a long time and yours is
    the best I have discovered till now. But, what about the bottom line?
    Are you sure in regards to the source?

    Comment by Lina — May 6, 2013 @ 5:06 pm | Reply

  7. Hi there to all, because I am really keen
    of reading this webpage’s post to be updated daily. It consists of pleasant data.

    Comment by Penelope — May 9, 2013 @ 2:30 pm | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: