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.

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:

 1<dependency>
 2    <groupId>org.codehaus.jackson</groupId>
 3    <artifactId>jackson-core-asl</artifactId>
 4    <version>1.9.9</version>
 5    <scope>test</scope>
 6</dependency>
 7<dependency>
 8    <groupId>org.codehaus.jackson</groupId>
 9    <artifactId>jackson-mapper-asl</artifactId>
10    <version>1.9.9</version>
11    <scope>test</scope>
12</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:

 1import org.hibernate.validator.constraints.Length;
 2import org.hibernate.validator.constraints.NotEmpty;
 3
 4public class TodoDTO {
 5
 6    private Long id;
 7
 8    @Length(max = 500)
 9    private String description;
10
11    @NotEmpty
12    @Length(max = 100)
13    private String title;
14
15    public TodoDTO() {
16
17    }
18
19    //Getters and setters
20}

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:

1public interface TodoService {
2
3    public Todo add(TodoDTO added);
4
5    public Todo update(TodoDTO updated) throws TodoNotFoundException;
6}

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:

 1import org.springframework.context.MessageSource;
 2import org.springframework.context.i18n.LocaleContextHolder;
 3import org.springframework.stereotype.Controller;
 4import org.springframework.ui.Model;
 5import org.springframework.validation.BindingResult;
 6import org.springframework.web.bind.annotation.*;
 7import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 8
 9import javax.annotation.Resource;
10import javax.validation.Valid;
11
12@Controller
13@SessionAttributes("todo")
14public class TodoController {
15
16    @Resource
17    private TodoService service;
18
19    @Resource
20    private MessageSource messageSource;
21
22    @RequestMapping(value = "/todo/add", method = RequestMethod.GET)
23    public String showAddTodoForm(Model model) {
24        TodoDTO formObject = new TodoDTO();
25        model.addAttribute("todo", formObject);
26
27        return "todo/add";
28    }
29
30    @RequestMapping(value = "/todo/add", method = RequestMethod.POST)
31    public String add(@Valid @ModelAttribute("todo") TodoDTO dto, BindingResult result, RedirectAttributes attributes) {
32        if (result.hasErrors()) {
33            return "todo/add";
34        }
35
36        Todo added = service.add(dto);
37
38        addFeedbackMessage(attributes, "feedback.message.todo.added", added.getTitle());
39        attributes.addAttribute("id", added.getId());
40
41        return createRedirectViewPath("/todo/{id}");
42    }
43
44    @RequestMapping(value = "/todo/update/{id}", method = RequestMethod.GET)
45    public String showUpdateTodoForm(@PathVariable("id") Long id, Model model) throws TodoNotFoundException {
46        Todo updated = service.findById(id);
47
48        TodoDTO formObject = constructFormObjectForUpdateForm(updated);
49        model.addAttribute("todo", formObject);
50
51        return "todo/update";
52    }
53
54    @RequestMapping(value = "/todo/update", method = RequestMethod.POST)
55    public String update(@Valid @ModelAttribute("todo") TodoDTO dto, BindingResult result, RedirectAttributes attributes) throws TodoNotFoundException {
56        if (result.hasErrors()) {
57            return "todo/update";
58        }
59
60        Todo updated = service.update(dto);
61
62        addFeedbackMessage(attributes, "feedback.message.todo.updated", updated.getTitle());
63        attributes.addAttribute("id", updated.getId());
64
65        return createRedirectViewPath("/todo/{id}");
66    }
67
68    private TodoDTO constructFormObjectForUpdateForm(Todo updated) {
69        TodoDTO dto = new TodoDTO();
70
71        dto.setId(updated.getId());
72        dto.setDescription(updated.getDescription());
73        dto.setTitle(updated.getTitle());
74
75        return dto;
76    }
77
78    private void addFeedbackMessage(RedirectAttributes attributes, String messageCode, Object... messageParameters) {
79        String localizedFeedbackMessage = getMessage(messageCode, messageParameters);
80        attributes.addFlashAttribute("feedbackMessage", localizedFeedbackMessage);
81    }
82
83    private String getMessage(String messageCode, Object... messageParameters) {
84        Locale current = LocaleContextHolder.getLocale();
85        return messageSource.getMessage(messageCode, messageParameters, current);
86    }
87
88    private String createRedirectViewPath(String requestMapping) {
89        StringBuilder redirectViewPath = new StringBuilder();
90        redirectViewPath.append("redirect:");
91        redirectViewPath.append(requestMapping);
92        return redirectViewPath.toString();
93    }
94}

Writing Integration Tests for Forms

The section describes how we can write integration tests for the forms of our todo application. Let’s move on and take a look at the common testing utilities which we use in our integration tests.

Common Testing Utilities

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

  • The TodoTestUtil class is used in both unit and integration tests of our todo application.
  • 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 one static method which is used used in our integration tests. The createStringWithLength(int length) method is used to create new String objects.

The source code of the TodoTestUtil class looks as follows:

 1public class TodoTestUtil {
 2
 3    public static String createStringWithLength(int length) {
 4        StringBuilder builder = new StringBuilder();
 5
 6        for (int index = 0; index < length; index++) {
 7            builder.append("a");
 8        }
 9
10        return builder.toString();
11    }
12}

The DBUnit Dataset

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

1<dataset>
2    <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"/>
3    <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"/>
4</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:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import org.junit.Test;
 5import org.junit.runner.RunWith;
 6import org.springframework.test.context.ContextConfiguration;
 7import org.springframework.test.context.TestExecutionListeners;
 8import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 9import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
10import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
11import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
12import org.springframework.test.web.server.MockMvc;
13import org.springframework.test.web.server.samples.context.WebContextLoader;
14
15import static org.hamcrest.Matchers.*;
16import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
17import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
18
19@RunWith(SpringJUnit4ClassRunner.class)
20@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
21@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
22        DirtiesContextTestExecutionListener.class,
23        TransactionalTestExecutionListener.class,
24        DbUnitTestExecutionListener.class })
25@DatabaseSetup("toDoData.xml")
26public class ITTodoControllerTest {
27
28    //Add web application context here
29
30    private MockMvc mockMvc;
31
32    //Add setUp() method here
33
34    @Test
35    @ExpectedDatabase("toDoData.xml")
36    public void showAddTodoForm() throws Exception {
37        mockMvc.perform(get("/todo/add"))
38                .andExpect(status().isOk())
39                .andExpect(view().name("todo/add"))
40                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
41                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
42                .andExpect(model().attribute("todo", hasProperty("description", isEmptyOrNullString())))
43                .andExpect(model().attribute("todo", hasProperty("title", isEmptyOrNullString())));
44    }
45}

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. Perform a POST request to url ‘/todo/add’ by following these steps:
  3. Set the content type of the request to ‘application/x-www-form-urlencoded’.
  4. Set a new TodoDTO object to session. This is required because our controller is annotated with the @SessionAttributes annotation.
  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 is a field error in the title field.
  9. Verify that our form object is empty.

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

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import org.junit.Test;
 5import org.junit.runner.RunWith;
 6import org.springframework.http.MediaType;
 7import org.springframework.test.context.ContextConfiguration;
 8import org.springframework.test.context.TestExecutionListeners;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
11import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
12import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
13import org.springframework.test.web.server.MockMvc;
14import org.springframework.test.web.server.samples.context.WebContextLoader;
15
16import static org.hamcrest.Matchers.*;
17import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
18import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
19
20@RunWith(SpringJUnit4ClassRunner.class)
21@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
22@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
23        DirtiesContextTestExecutionListener.class,
24        TransactionalTestExecutionListener.class,
25        DbUnitTestExecutionListener.class })
26@DatabaseSetup("toDoData.xml")
27public class ITTodoControllerTest {
28
29    //Add web application context here
30
31    private MockMvc mockMvc;
32
33    //Add setUp() method here
34
35    @Test
36    @ExpectedDatabase("toDoData.xml")
37    public void addEmptyTodo() throws Exception {
38        mockMvc.perform(post("/todo/add")
39                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
40                .sessionAttr("todo", new TodoDTO())
41        )
42                .andExpect(status().isOk())
43                .andExpect(view().name("todo/add"))
44                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
45                .andExpect(model().attributeHasFieldErrors("todo", "title"))
46                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
47                .andExpect(model().attribute("todo", hasProperty("description", isEmptyOrNullString())))
48                .andExpect(model().attribute("todo", hasProperty("title", isEmptyOrNullString())));
49    }
50}

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. Perform a POST request to url ‘/todo/add’ by following these steps:
  4. Set the content type of the request to ‘application/x-www-form-urlencoded’.
  5. Send the description and title of the todo entry as request parameters.
  6. Set a new TodoDTO object to session. This is required because our controller is annotated with the @SessionAttributes annotation.
  7. Verify that the HTTP status code is 200.
  8. Verify that the name of the rendered view is ‘/todo/add’.
  9. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/add.jsp’.
  10. Verify that there are a field errors in the title and description fields.
  11. Verify that our form object contains the correct values.

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

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import org.junit.Test;
 5import org.junit.runner.RunWith;
 6import org.springframework.http.MediaType;
 7import org.springframework.test.context.ContextConfiguration;
 8import org.springframework.test.context.TestExecutionListeners;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
11import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
12import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
13import org.springframework.test.web.server.MockMvc;
14import org.springframework.test.web.server.samples.context.WebContextLoader;
15
16import static org.hamcrest.Matchers.*;
17import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
18import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
19
20@RunWith(SpringJUnit4ClassRunner.class)
21@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
22@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
23        DirtiesContextTestExecutionListener.class,
24        TransactionalTestExecutionListener.class,
25        DbUnitTestExecutionListener.class })
26@DatabaseSetup("toDoData.xml")
27public class ITTodoControllerTest {
28
29    //Add web application context here
30
31    private MockMvc mockMvc;
32
33    //Add setUp() method here
34
35    @Test
36    @ExpectedDatabase("toDoData.xml")
37    public void addTodoWhenTitleAndDescriptionAreTooLong() throws Exception {
38        String title = TodoTestUtil.createStringWithLength(101);
39        String description = TodoTestUtil.createStringWithLength(501);
40
41        mockMvc.perform(post("/todo/add")
42                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
43                .param("description", description)
44                .param("title", title)
45                .sessionAttr("todo", new TodoDTO())
46        )
47                .andExpect(status().isOk())
48                .andExpect(view().name("todo/add"))
49                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
50                .andExpect(model().attributeHasFieldErrors("todo", "title"))
51                .andExpect(model().attributeHasFieldErrors("todo", "description"))
52                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
53                .andExpect(model().attribute("todo", hasProperty("description", is(description))))
54                .andExpect(model().attribute("todo", hasProperty("title", is(title))));
55    }
56}

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. Perform a POST request to url ‘/todo/add’ by following these steps:
  3. Set the content type of the request to ‘application/x-www-form-urlencoded’.
  4. Send the description and title of the todo entry as request parameters.
  5. Set a new TodoDTO object to session. This is required because our controller is annotated with the @SessionAttributes annotation.
  6. Verify that the HTTP status code is 200.
  7. Verify that the name of the rendered view is ‘redirect:/todo/view/{id}’.
  8. Verify that the model has an attribute caled ‘id’ and that its value is 3.
  9. Verify that the correct feedback message is given as a flash attribute.

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

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
 5import org.junit.Test;
 6import org.junit.runner.RunWith;
 7import org.springframework.http.MediaType;
 8import org.springframework.test.context.ContextConfiguration;
 9import org.springframework.test.context.TestExecutionListeners;
10import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
12import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
13import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
14import org.springframework.test.web.server.MockMvc;
15import org.springframework.test.web.server.samples.context.WebContextLoader;
16
17import static org.hamcrest.Matchers.is;
18import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
19import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
20
21@RunWith(SpringJUnit4ClassRunner.class)
22@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
23@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
24        DirtiesContextTestExecutionListener.class,
25        TransactionalTestExecutionListener.class,
26        DbUnitTestExecutionListener.class })
27@DatabaseSetup("toDoData.xml")
28public class ITTodoControllerTest {
29
30    //Add web application context here
31
32    private MockMvc mockMvc;
33
34    //Add setUp() method here
35
36    @Test
37    @ExpectedDatabase(value="toDoData-add-expected.xml", assertionMode = DatabaseAssertionMode.NON_STRICT)
38    public void addTodo() throws Exception {
39        mockMvc.perform(post("/todo/add")
40                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
41                .param("description", "description")
42                .param("title", "title")
43                .sessionAttr("todo", new TodoDTO())
44        )
45                .andExpect(status().isOk())
46                .andExpect(view().name("redirect:/todo/view/{id}"))
47                .andExpect(model().attribute("id", is("3")))
48                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: title was added.")));
49    }
50}

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

1<dataset>
2    <todos id="1" description="Lorem ipsum" title="Foo" version="0"/>
3    <todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
4    <todos id="3" description="description" title="title" version="0"/>
5</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:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import org.junit.Test;
 5import org.junit.runner.RunWith;
 6import org.springframework.test.context.ContextConfiguration;
 7import org.springframework.test.context.TestExecutionListeners;
 8import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 9import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
10import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
11import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
12import org.springframework.test.web.server.MockMvc;
13import org.springframework.test.web.server.samples.context.WebContextLoader;
14
15import static org.hamcrest.Matchers.*;
16import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
17import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
18
19@RunWith(SpringJUnit4ClassRunner.class)
20@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
21@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
22        DirtiesContextTestExecutionListener.class,
23        TransactionalTestExecutionListener.class,
24        DbUnitTestExecutionListener.class })
25@DatabaseSetup("toDoData.xml")
26public class ITTodoControllerTest {
27
28    //Add web application context here
29
30    private MockMvc mockMvc;
31
32    //Add setUp() method here
33
34    @Test
35    @ExpectedDatabase("toDoData.xml")
36    public void showUpdateTodoForm() throws Exception {
37        mockMvc.perform(get("/todo/update/{id}", 1L))
38                .andExpect(status().isOk())
39                .andExpect(view().name("todo/update"))
40                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/update.jsp"))
41                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
42                .andExpect(model().attribute("todo", hasProperty("description", is("Lorem ipsum"))))
43                .andExpect(model().attribute("todo", hasProperty("title", is("Foo"))));
44    }
45}

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:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import org.junit.Test;
 5import org.junit.runner.RunWith;
 6import org.springframework.test.context.ContextConfiguration;
 7import org.springframework.test.context.TestExecutionListeners;
 8import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 9import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
10import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
11import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
12import org.springframework.test.web.server.MockMvc;
13import org.springframework.test.web.server.samples.context.WebContextLoader;
14
15import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
16import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
17
18@RunWith(SpringJUnit4ClassRunner.class)
19@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
20@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
21        DirtiesContextTestExecutionListener.class,
22        TransactionalTestExecutionListener.class,
23        DbUnitTestExecutionListener.class })
24@DatabaseSetup("toDoData.xml")
25public class ITTodoControllerTest {
26
27    //Add web application context here
28
29    private MockMvc mockMvc;
30
31    //Add setUp() method here
32
33    @Test
34    @ExpectedDatabase("toDoData.xml")
35    public void showUpdateTodoFormWhenTodoIsNotFound() throws Exception {
36        mockMvc.perform(get("/todo/update/{id}", 3L))
37                .andExpect(status().isNotFound())
38                .andExpect(view().name("error/404"))
39                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));
40    }
41}

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. Perform a POST request to url ‘/todo/update’ by following these steps:
  3. Set the content type of the request to ‘application/x-www-form-urlencoded’.
  4. Send the id of the todo entry as a request parameter
  5. Set a new TodoDTO object to session. This is required because our controller is annotated with the @SessionAttributes annotation.
  6. Verify that the HTTP status code is 200.
  7. Verify that the name of the rendered view is ‘/todo/update’.
  8. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/update.jsp’.
  9. Verify that there is a field error in the title field.
  10. Verify that our form object is empty.

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

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import org.junit.Test;
 5import org.junit.runner.RunWith;
 6import org.springframework.http.MediaType;
 7import org.springframework.test.context.ContextConfiguration;
 8import org.springframework.test.context.TestExecutionListeners;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
11import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
12import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
13import org.springframework.test.web.server.MockMvc;
14import org.springframework.test.web.server.samples.context.WebContextLoader;
15
16import static org.hamcrest.Matchers.*;
17import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
18import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
19
20@RunWith(SpringJUnit4ClassRunner.class)
21@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
22@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
23        DirtiesContextTestExecutionListener.class,
24        TransactionalTestExecutionListener.class,
25        DbUnitTestExecutionListener.class })
26@DatabaseSetup("toDoData.xml")
27public class ITTodoControllerTest {
28
29    //Add web application context here
30
31    private MockMvc mockMvc;
32
33    //Add setUp() method here
34
35    @Test
36    @ExpectedDatabase("toDoData.xml")
37    public void updateEmptyTodo() throws Exception {
38        mockMvc.perform(post("/todo/update")
39                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
40                .param("id", "1")
41                .sessionAttr("todo", new TodoDTO())
42        )
43                .andExpect(status().isOk())
44                .andExpect(view().name("todo/update"))
45                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/update.jsp"))
46                .andExpect(model().attributeHasFieldErrors("todo", "title"))
47                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
48                .andExpect(model().attribute("todo", hasProperty("description", isEmptyOrNullString())))
49                .andExpect(model().attribute("todo", hasProperty("title", isEmptyOrNullString())));
50    }
51}

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. Perform a POST request to url ‘/todo/update’ by following these steps:
  4. Set the content type of the request to ‘application/x-www-form-urlencoded’.
  5. Send the description, id, and title of the todo entry as request parameters
  6. Set a new TodoDTO object to session. This is required because our controller is annotated with the @SessionAttributes annotation.
  7. Verify that the HTTP status code is 200.
  8. Verify that the name of the rendered view is ‘/todo/update’.
  9. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/update.jsp’.
  10. Verify that there are field errors in the title and description fields.
  11. Verify that our form object contains the correct values.

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

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import org.junit.Test;
 5import org.junit.runner.RunWith;
 6import org.springframework.http.MediaType;
 7import org.springframework.test.context.ContextConfiguration;
 8import org.springframework.test.context.TestExecutionListeners;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
11import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
12import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
13import org.springframework.test.web.server.MockMvc;
14import org.springframework.test.web.server.samples.context.WebContextLoader;
15
16import static org.hamcrest.Matchers.*;
17import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
18import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
19
20@RunWith(SpringJUnit4ClassRunner.class)
21@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
22@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
23        DirtiesContextTestExecutionListener.class,
24        TransactionalTestExecutionListener.class,
25        DbUnitTestExecutionListener.class })
26@DatabaseSetup("toDoData.xml")
27public class ITTodoControllerTest {
28
29    //Add web application context here
30
31    private MockMvc mockMvc;
32
33    //Add setUp() method here
34
35    @Test
36    @ExpectedDatabase("toDoData.xml")
37    public void updateTodoWhenTitleAndDescriptionAreTooLong() throws Exception {
38        String title = TodoTestUtil.createStringWithLength(101);
39        String description = TodoTestUtil.createStringWithLength(501);
40
41        mockMvc.perform(post("/todo/update")
42                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
43                .param("description", description)
44                .param("id", "1")
45                .param("title", title)
46                .sessionAttr("todo", new TodoDTO())
47        )
48                .andExpect(status().isOk())
49                .andExpect(view().name("todo/update"))
50                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/update.jsp"))
51                .andExpect(model().attributeHasFieldErrors("todo", "title"))
52                .andExpect(model().attributeHasFieldErrors("todo", "description"))
53                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
54                .andExpect(model().attribute("todo", hasProperty("description", is(description))))
55                .andExpect(model().attribute("todo", hasProperty("title", is(title))));
56    }
57}

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. Perform a POST request to url ‘/todo/update’ by following these steps:
  3. Set the content type of the request to ‘application/x-www-form-urlencoded’.
  4. Send the description, id, and title of the todo entry as request parameters
  5. Set a new TodoDTO object to session. This is required because our controller is annotated with the @SessionAttributes annotation.
  6. Verify that the HTTP status code is 200.
  7. Verify that the name of the rendered view is ‘redirect:/todo/view/{id}’.
  8. Verify that the model has an attribute ‘id’ and that its value is 1.
  9. Verify that the correct feedback message is given as a flash attribute.

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

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
 5import org.junit.Test;
 6import org.junit.runner.RunWith;
 7import org.springframework.http.MediaType;
 8import org.springframework.test.context.ContextConfiguration;
 9import org.springframework.test.context.TestExecutionListeners;
10import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
12import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
13import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
14import org.springframework.test.web.server.MockMvc;
15import org.springframework.test.web.server.samples.context.WebContextLoader;
16
17import static org.hamcrest.Matchers.is;
18import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
19import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
20
21@RunWith(SpringJUnit4ClassRunner.class)
22@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
23@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
24        DirtiesContextTestExecutionListener.class,
25        TransactionalTestExecutionListener.class,
26        DbUnitTestExecutionListener.class })
27@DatabaseSetup("toDoData.xml")
28public class ITTodoControllerTest {
29
30    //Add web application context here
31
32    private MockMvc mockMvc;
33
34    //Add setUp() method here
35
36    @Test
37    @ExpectedDatabase(value="toDoData-update-expected.xml", assertionMode = DatabaseAssertionMode.NON_STRICT)
38    public void updateTodo() throws Exception {
39        mockMvc.perform(post("/todo/update")
40                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
41                .param("description", "description")
42                .param("id", "1")
43                .param("title", "title")
44                .sessionAttr("todo", new TodoDTO())
45        )
46                .andExpect(status().isOk())
47                .andExpect(view().name("redirect:/todo/view/{id}"))
48                .andExpect(model().attribute("id", is("1")))
49                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: title was updated.")));
50    }
51}

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

1<dataset>
2    <todos id="1" description="description" title="title" version="1"/>
3    <todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
4</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. Perform a POST request to url ‘/todo/update’ by following these steps:
  3. Set the content type of the request to ‘application/x-www-form-urlencoded’.
  4. Send the description, id, and title of the todo entry as request parameters
  5. Set a new TodoDTO object to session. This is required because our controller is annotated with the @SessionAttributes annotation.
  6. Verify that the HTTP status code is 404.
  7. Verify that the name of the rendered view is ’error/404’.
  8. 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:

 1import com.github.springtestdbunit.DbUnitTestExecutionListener;
 2import com.github.springtestdbunit.annotation.DatabaseSetup;
 3import com.github.springtestdbunit.annotation.ExpectedDatabase;
 4import org.junit.Test;
 5import org.junit.runner.RunWith;
 6import org.springframework.http.MediaType;
 7import org.springframework.test.context.ContextConfiguration;
 8import org.springframework.test.context.TestExecutionListeners;
 9import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
11import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
12import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
13import org.springframework.test.web.server.MockMvc;
14import org.springframework.test.web.server.samples.context.WebContextLoader;
15
16import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
17import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;
18
19@RunWith(SpringJUnit4ClassRunner.class)
20@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
21@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
22        DirtiesContextTestExecutionListener.class,
23        TransactionalTestExecutionListener.class,
24        DbUnitTestExecutionListener.class })
25@DatabaseSetup("toDoData.xml")
26public class ITTodoControllerTest {
27
28    //Add web application context here
29
30    private MockMvc mockMvc;
31
32    //Add setUp() method here
33
34    @Test
35    @ExpectedDatabase("toDoData.xml")
36    public void updateTodoWhenTodoIsNotFound() throws Exception {
37        mockMvc.perform(post("/todo/update")
38                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
39                .param("description", "description")
40                .param("id", "3")
41                .param("title", "title")
42                .sessionAttr("todo", new TodoDTO())
43        )
44                .andExpect(status().isNotFound())
45                .andExpect(view().name("error/404"))
46                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));
47    }
48}

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 we can specify the content type of the request.
  • We know how to send the values of the form fields as request parameters.
  • 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. You can get the example application of this blog post from Github.

If you want to save time by writing less test code, take a look at my upcoming Test With Spring Course.