Integration Testing of Spring MVC Applications: Forms

The final piece of the puzzle

This is the third part of my spring-test-mvc tutorial and it describes how we can write integration tests for controller methods that are processing form submissions.

During this tutorial we will continue writing integration tests for a simple todo application. This tutorial concentrates on two functions that are used to create new todo entries and update the information of existing todo entries.

Note: This blog entry assumes that we are familiar with the concepts described in the first and second part of this tutorial.

Getting the Required Dependencies

Our tests use Jackson to convert form objects in to strings that are send in the body of the performed POST request. Thus, we have to declare the Jackson dependencies in our pom.xml file. We can do this by following these steps:

  1. Declare the jackson-core-asl (version 1.9.9) dependency in our pom.xml file.
  2. Declare the jackson-mapper-asl (version 1.9.9) dependency in our pom.xml file.

We can do this by adding the following snippet to our pom.xml file:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-core-asl</artifactId>
    <version>1.9.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.9</version>
    <scope>test</scope>
</dependency>

The Anatomy of Our Todo Application

Before we can write integration tests for our todo application, we need to take a quick look at its anatomy. This section describes the used form object, the used service interface and the controller implementation.

The Form Object

The form object of our todo application is rather simple. It has only a few fields, and its only methods are simple getters and setters. It also declares the following validation rules:

  • The title of an todo entry cannot be empty.
  • The maximum length of the todo entry’s title is 100 characters.
  • The maximum length of the todo entry’s description is 500 characters.

The source code of the TodoDTO class looks as follows:

import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;

public class TodoDTO {

    private Long id;

    @Length(max = 500)
    private String description;

    @NotEmpty
    @Length(max = 100)
    private String title;

    public TodoDTO() {

    }

    //Getters and setters
}

The Service Interface

If we want to understand the implementation of our controller methods, we have to understand the contract between our controller class and the service layer. This contract is described by the TodoService interface that declares two new methods:

  • The Todo add(TodoDTO added) method adds a new todo entry and returns a the added entry.
  • The Todo update(TodoDTO updated) method updates the information of a single todo entry and returns the updated entry. If no todo entry is found, this method throws TodoNotFoundException.

The source code of the TodoService interface looks as follows:

public interface TodoService {

    public Todo add(TodoDTO added);

    public Todo update(TodoDTO updated) throws TodoNotFoundException;
}

The Controller

The TodoController class has four methods that processes requests that are related to adding and updating todo entries. These methods are described in the following:

  • The showAddTodoForm() method shows the page that contains the add todo entry form.
  • The add() methods processes the form submissions of the add todo form.
  • The showUpdateTodoForm() method shows the page that contains the update todo entry form.
  • The update() method processes the form submissions of the update todo form.

The source code of the TodoController class looks as follows:

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import javax.validation.Valid;

@Controller
@SessionAttributes("todo")
public class TodoController {

    @Resource
    private TodoService service;

    @Resource
    private MessageSource messageSource;

    @RequestMapping(value = "/todo/add", method = RequestMethod.GET)
    public String showAddTodoForm(Model model) {
        TodoDTO formObject = new TodoDTO();
        model.addAttribute("todo", formObject);

        return "todo/add";
    }

    @RequestMapping(value = "/todo/add", method = RequestMethod.POST)
    public String add(@Valid @ModelAttribute("todo") TodoDTO dto, BindingResult result, RedirectAttributes attributes) {
        if (result.hasErrors()) {
            return "todo/add";
        }

        Todo added = service.add(dto);

        addFeedbackMessage(attributes, "feedback.message.todo.added", added.getTitle());
        attributes.addAttribute("id", added.getId());

        return createRedirectViewPath("/todo/{id}");
    }

    @RequestMapping(value = "/todo/update/{id}", method = RequestMethod.GET)
    public String showUpdateTodoForm(@PathVariable("id") Long id, Model model) throws TodoNotFoundException {
        Todo updated = service.findById(id);

        TodoDTO formObject = constructFormObjectForUpdateForm(updated);
        model.addAttribute("todo", formObject);

        return "todo/update";
    }

    @RequestMapping(value = "/todo/update", method = RequestMethod.POST)
    public String update(@Valid @ModelAttribute("todo") TodoDTO dto, BindingResult result, RedirectAttributes attributes) throws TodoNotFoundException {
        if (result.hasErrors()) {
            return "todo/update";
        }

        Todo updated = service.update(dto);

        addFeedbackMessage(attributes, "feedback.message.todo.updated", updated.getTitle());
        attributes.addAttribute("id", updated.getId());

        return createRedirectViewPath("/todo/{id}");
    }

    private TodoDTO constructFormObjectForUpdateForm(Todo updated) {
        TodoDTO dto = new TodoDTO();

        dto.setId(updated.getId());
        dto.setDescription(updated.getDescription());
        dto.setTitle(updated.getTitle());

        return dto;
    }

    private void addFeedbackMessage(RedirectAttributes attributes, String messageCode, Object... messageParameters) {
        String localizedFeedbackMessage = getMessage(messageCode, messageParameters);
        attributes.addFlashAttribute("feedbackMessage", localizedFeedbackMessage);
    }

    private String getMessage(String messageCode, Object... messageParameters) {
        Locale current = LocaleContextHolder.getLocale();
        return messageSource.getMessage(messageCode, messageParameters, current);
    }


    private String createRedirectViewPath(String requestMapping) {
        StringBuilder redirectViewPath = new StringBuilder();
        redirectViewPath.append("redirect:");
        redirectViewPath.append(requestMapping);
        return redirectViewPath.toString();
    }
}

Writing Integration Tests for Forms

The section describes how we can write integration tests for the forms of our todo application. Lets move on and take a look at the common testing utilities that are used in our integration tests.

Common Testing Utilities

We use three testing utilities in our integration tests. These test utilities are:

  • The TodoTestUtil class is used in both the unit tests and integration tests of our todo application.
  • The IntegrationTestUtil class is used only in our integration tests.
  • The toDoData.xml is a DBUnit dataset that initializes the used database to a known state before our tests are run.

These utilities are described with more details in following.

The TodoTestUtil Class

The TodoTestUtil class has two static methods that are used used in our integration tests. These methods are described in following:

  • The TodoDTO createFormObject(Long id, String description, String title) method creates a new form objects.
  • The String createStringWithLength(int length) method creates new String objects.

The source code of the TodoTestUtil class is given in the following:

public class TodoTestUtil {

    public static TodoDTO createFormObject(Long id, String description, String title) {
        TodoDTO dto = new TodoDTO();

        dto.setId(id);
        dto.setDescription(description);
        dto.setTitle(title);

        return dto;
    }

    public static String createStringWithLength(int length) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < length; index++) {
            builder.append("a");
        }

        return builder.toString();
    }
}

The IntegrationTestUtil Class

The IntegrationTestUtil class has one method that is used in our integration tests. This method converts form objects to strings which mime type is ‘application/x-www-form-urlencoded’. This method is called convertObjectToFormUrlEncodedBytes() and its implementation has following steps:

  1. Create a new ObjectMapper object and configure it to include only non null properties of the serialized object.
  2. Convert the form object to a map that uses the names of its properties as keys.
  3. Get the names of the form object’s properties from the map.
  4. Build the string by iterating the obtained property names.
  5. Return the created string as byte array.

The source code of the IntegrationTestUtil class looks as follows:

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;

public class IntegrationTestUtil {
    public static byte[] convertObjectToFormUrlEncodedBytes(Object object) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);

        Map<String, Object> propertyValues = mapper.convertValue(object, Map.class);

        Set<String> propertyNames = propertyValues.keySet();
        Iterator<String> nameIter = propertyNames.iterator();

        StringBuilder formUrlEncoded = new StringBuilder();

        for (int index=0; index < propertyNames.size(); index++) {
            String currentKey = nameIter.next();
            Object currentValue = propertyValues.get(currentKey);

            formUrlEncoded.append(currentKey);
            formUrlEncoded.append("=");
            formUrlEncoded.append(currentValue);

            if (nameIter.hasNext()) {
                formUrlEncoded.append("&");
            }
        }

        return formUrlEncoded.toString().getBytes();
    }
}

The DBUnit Dataset

Each integration test uses the same DBUnit dataset which is found from the toDoData.xml. The content of this dataset looks as follows:

<dataset>
    <todos id="1" creation_time="2012-10-21 11:13:28" description="Lorem ipsum" modification_time="2012-10-21 11:13:28" title="Foo" version="0"/>
    <todos id="2" creation_time="2012-10-21 11:13:28" description="Lorem ipsum" modification_time="2012-10-21 11:13:28" title="Bar" version="0"/>
</dataset>

Show Add Todo Form

The showAddTodoForm() method of the TodoController class is used to view the page that contains the add todo form. We can write an integration test for this method by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
  2. Perform a GET request to url ‘/todo/add’.
  3. Verify that the HTTP status code is 200.
  4. Verify that name of the rendered view is ‘todo/add’.
  5. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/add.jsp’.
  6. Verify that each field of our form object is empty.

The source code of our integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void showAddTodoForm() throws Exception {
        mockMvc.perform(get("/todo/add"))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/add"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
                .andExpect(model().attribute("todo", hasProperty("description", isEmptyOrNullString())))
                .andExpect(model().attribute("todo", hasProperty("title", isEmptyOrNullString())));
    }
}

Add Todo

The add() method of the TodoController class is responsible of processing the form submissions of the add todo form. We have to write three integration tests for this method. These tests are described in following:

  • We have to write a test that ensures that the method is working correctly when an empty add todo form is submitted.
  • We have to write a test that ensures that the method is working properly when the title and the description of the todo entry are too long, and the add todo form is submitted.
  • We have to write a test that ensures that this method is working correctly when a new todo entry is added.

These tests are described with more details in following.

Submit an Empty Add Todo Form

We can write the first integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
  2. Create a new TodoDTO object.
  3. Perform a POST request to url ‘/todo/add’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set the form object to the used session.
  4. Verify that the HTTP status code is 200.
  5. Verify that the name of the rendered view is ‘/todo/add’.
  6. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/add.jsp’.
  7. Verify that there is a field error in the title field.
  8. Verify that our form object is empty.

The source code of our first integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void addEmptyTodo() throws Exception {
        TodoDTO formObject = new TodoDTO();
        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/add"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
                .andExpect(model().attribute("todo", hasProperty("description", isEmptyOrNullString())))
                .andExpect(model().attribute("todo", hasProperty("title", isEmptyOrNullString())));
    }
}

Submit Add Todo Form with Validation Errors

We can write the second integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
  2. Create title and description of the todo entry.
  3. Create a new TodoDTO object and set its title and description.
  4. Perform a POST request to url ‘/todo/add’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set the form object to the used session.
  5. Verify that the HTTP status code is 200.
  6. Verify that the name of the rendered view is ‘/todo/add’.
  7. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/add.jsp’.
  8. Verify that there are a field errors in the title and description fields.
  9. Verify that our form object contains the correct values.

The source code of our second integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void addTodoWhenTitleAndDescriptionAreTooLong() throws Exception {
        String title = TodoTestUtil.createStringWithLength(101);
        String description = TodoTestUtil.createStringWithLength(501);

        TodoDTO formObject = TodoTestUtil.createFormObject(null, description, title);

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/add"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attributeHasFieldErrors("todo", "description"))
                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
                .andExpect(model().attribute("todo", hasProperty("description", is(description))))
                .andExpect(model().attribute("todo", hasProperty("title", is(title))));
    }
}

Submit Add Todo Form

We can write the third integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to verify that a new todo entry is added to database.
  2. Create a new TodoDTO object that has the correct information.
  3. Perform a POST request to url ‘/todo/add’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set the form object to the used session.
  4. Verify that the HTTP status code is 200.
  5. Verify that the name of the rendered view is ‘redirect:/todo/view/{id}’.
  6. Verify that the model has an attribute caled ‘id’ and that its value is 3.
  7. Verify that the correct feedback message is given as a flash attribute.

The source code of our third integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase(value="toDoData-add-expected.xml", assertionMode = DatabaseAssertionMode.NON_STRICT)
    public void addTodo() throws Exception {
        TodoDTO formObject = TodoTestUtil.createFormObject(null, "description", "title");

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("redirect:/todo/view/{id}"))
                .andExpect(model().attribute("id", is("3")))
                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: title was added.")));
    }
}

The name of the used DBUnit dataset is toDoData-add-expected.xml and its content is given in the following:

<dataset>
    <todos id="1" description="Lorem ipsum" title="Foo" version="0"/>
    <todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
    <todos id="3" description="description" title="title" version="0"/>
</dataset>

Show Update Todo Form

The showUpdateTodoForm() method of the TodoController class views the page that contains the update todo form. We have to write two integration tests for this method. These tests are described in the following:

  • We have to write a test that ensures that this method is working correctly when the updated todo entry is found.
  • We have to write a test that ensures that this method is working properly when the updated todo entry is not found.

These tests are described with more details in following.

Show Update Todo Form

We can write the first integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
  2. Perform a GET request to url ‘/todo/update/1′.
  3. Verify that the HTTP status code is 200.
  4. Verify that the name of the rendered view is ‘todo/update’.
  5. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/update.jsp’.
  6. Verify that our form object contains the correct information.

The source code of our first integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void showUpdateTodoForm() throws Exception {
        mockMvc.perform(get("/todo/update/{id}", 1L))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/update"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/update.jsp"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", is("Lorem ipsum"))))
                .andExpect(model().attribute("todo", hasProperty("title", is("Foo"))));
    }
}

Show Update Todo Form When the Todo Entry Is Not Found

We can write the second integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
  2. Perform a GET to request to url ‘/todo/update/3′.
  3. Verify that the HTTP status code is 404.
  4. Verify that the name of the rendered view is ‘error/404′.
  5. Verify that the request is forwarded to url ‘/WEB-INF/jsp/error/404.jsp’.

The source code of our second integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void showUpdateTodoFormWhenTodoIsNotFound() throws Exception {
        mockMvc.perform(get("/todo/update/{id}", 3L))
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));
    }
}

Update Todo

The update() method of the TodoController class processes the form submissions of the update todo form. We have to write four integration tests for this method. These integration tests are described in the following:

  • We have to write an integration test that ensures that the method is working properly when an empty update todo form is submitted.
  • We have to write an integration test that ensures the method is working properly when the title and description of the todo entry are too long, and the update todo form is submitted.
  • We have to write an integration test that ensures that the method is working correctly when the information of a todo entry is updated.
  • We have to write an integration test that ensures that the method is working properly when the updated todo entry is not found.

These tests are described with more details in following.

Submit an Empty Update Todo Form

We can write the first integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to verify that no changes are made to the database.
  2. Create a TodoDTO object and set the value of its id field.
  3. Perform a POST request to url ‘/todo/update’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set form object to the used session.
  4. Verify that the HTTP status code is 200.
  5. Verify that the name of the rendered view is ‘/todo/update’.
  6. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/update.jsp’.
  7. Verify that there is a field error in the title field.
  8. Verify that our form object is empty.

The source code of our first integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void updateEmptyTodo() throws Exception {
        TodoDTO formObject = TodoTestUtil.createFormObject(1L, null, null);
        mockMvc.perform(post("/todo/update")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/update"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/update.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", isEmptyOrNullString())))
                .andExpect(model().attribute("todo", hasProperty("title", isEmptyOrNullString())));
    }
}

Submit Update Todo Form with Validation Errors

We can write the second integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
  2. Create the title and the description of the todo entry.
  3. Create a new TodoDTO object and set its id, title and description.
  4. Perform a POST request to url ‘/todo/update’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set form object to the used session.
  5. Verify that the HTTP status code is 200.
  6. Verify that the name of the rendered view is ‘/todo/update’.
  7. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/update.jsp’.
  8. Verify that there are field errors in the title and description fields.
  9. Verify that our form object contains the correct values.

The source code of our second integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void updateTodoWhenTitleAndDescriptionAreTooLong() throws Exception {
        String title = TodoTestUtil.createStringWithLength(101);
        String description = TodoTestUtil.createStringWithLength(501);

        TodoDTO formObject = TodoTestUtil.createFormObject(1L, description, title);

        mockMvc.perform(post("/todo/update")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/update"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/update.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attributeHasFieldErrors("todo", "description"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", is(description))))
                .andExpect(model().attribute("todo", hasProperty("title", is(title))));
    }
}

Submit Update Todo Form

We can write the third integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to verify that the information of the todo entry is updated.
  2. Create a new TodoDTO object that has the correct information.
  3. Perform a POST request to url ‘/todo/update’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set form object to the used session.
  4. Verify that the HTTP status code is 200.
  5. Verify that the name of the rendered view is ‘redirect:/todo/view/{id}’.
  6. Verify that the model has an attribute ‘id’ and that its value is 1.
  7. Verify that the correct feedback message is given as a flash attribute.

The source code of our third integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase(value="toDoData-update-expected.xml", assertionMode = DatabaseAssertionMode.NON_STRICT)
    public void updateTodo() throws Exception {
        TodoDTO formObject = TodoTestUtil.createFormObject(1L, "description", "title");

        mockMvc.perform(post("/todo/update")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("redirect:/todo/view/{id}"))
                .andExpect(model().attribute("id", is("1")))
                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: title was updated.")));
    }
}

The name of the used DBUnit dataset is toDoData-update-expected.xml and its content is given in the following:

<dataset>
    <todos id="1" description="description" title="title" version="1"/>
    <todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
</dataset>

Submit Update Todo Form When the Todo Entry Is Not Found

We can write the fourth integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to verify that no changes is made to the database.
  2. Create a new TodoDTO object and set its id, title and description. Ensure that no todo entry is found with its id.
  3. Perform a POST request to url ‘/todo/update’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set form object to the used session.
  4. Verify that the HTTP status code is 404.
  5. Verify that the name of the rendered view is ‘error/404′.
  6. Verify that the request is forwarded to url ‘/WEB-INF/jsp/error/404.jsp’.

The source code of our fourth integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add data source and web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void updateTodoWhenTodoIsNotFound() throws Exception {
        TodoDTO formObject = TodoTestUtil.createFormObject(3L, "description", "title");

        mockMvc.perform(post("/todo/update")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));
    }
}

Conclusion

We have now learned how we can write integration tests for controller methods that processes form submissions. This tutorial has taught us three things:

  • We know how to set a body of a HTTP request and specify its content type.
  • We know how to add values to the session used in our integration test.
  • We know how we can check that our form submission has field errors.

In the next part of this tutorial, we learn to write integration tests for a REST API that reads information from the database and returns it to a single page web application.

P.S. The source code of our todo application is available at Github.

About the Author

Petri Kainulainen is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

About Petri Kainulainen →

Related Posts

{ 3 comments… add one }

Leave a Comment