The previous part of my Spring MVC Test tutorial described how we can write unit tests for a Spring MVC controller that renders a list. This blog post provides more information about writing unit tests for Spring MVC controllers which insert data into the used database. To be more specific, this blog post describes how we can write unit tests for a Spring MVC controller that submits a form.

After we have finished this blog post, we:

  • Know how we can submit a form by using the Spring MVC Test framework.
  • Understand how we can ensure that the system under test displays the correct validation errors when we submit a form that contains invalid information.
  • Know how we can can ensure that the fields of the submitted form contain the correct information if validation fails.
  • Can verify that the HTTP request is redirected to the correct path.
  • Know how we can ensure that the system under test displays the correct flash message to the user.

Let’s begin.

Introduction to the System Under Test

We have to write unit tests for a controller method that processes POST requests send to the path: ‘/todo-items’. This method creates a new todo item and redirects the user to the view todo item view. If validation fails, this controller method returns the HTTP status code 200 and renders the form view.

The tested controller method is called create() and it’s implemented by following these steps:

  1. If the submitted form has validation errors, return the name of the form view (’todo-item/create’).
  2. Save the created todo item to the database by invoking the create() method of the TodoItemCrudService class.
  3. Create a feedback message which states that a new todo item was created and ensure that this message is shown when the next view is rendered.
  4. Redirect the HTTP request to the view that renders the information of the created todo item.

The source code of the tested controller method looks as follows:

 1import org.springframework.beans.factory.annotation.Autowired;
 2import org.springframework.context.MessageSource;
 3import org.springframework.stereotype.Controller;
 4import org.springframework.validation.BindingResult;
 5import org.springframework.web.bind.annotation.ModelAttribute;
 6import org.springframework.web.bind.annotation.PostMapping;
 7import org.springframework.web.bind.annotation.RequestMapping;
 8import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 9
10import javax.validation.Valid;
11import java.util.Locale;
12
13@Controller
14@RequestMapping("/todo-item")
15public class TodoItemCrudController {
16
17    private final MessageSource messageSource;
18    private final TodoItemCrudService service;
19
20    @Autowired
21    public TodoItemCrudController(MessageSource messageSource,
22                                  TodoItemCrudService service) {
23        this.messageSource = messageSource;
24        this.service = service;
25    }
26
27    @PostMapping
28    public String create(@Valid @ModelAttribute("todoItem") CreateTodoItemFormDTO form,
29                         BindingResult bindingResult,
30                         RedirectAttributes redirectAttributes,
31                         Locale currentLocale) {
32        if (bindingResult.hasErrors()) {
33            return "todo-item/create";
34        }
35
36        TodoItemDTO created = service.create(form);
37
38        addFeedbackMessage(
39                redirectAttributes,
40                "feedback.message.todoItem.created",
41                currentLocale,
42                created.getTitle()
43        );
44
45        redirectAttributes.addAttribute("id", created.getId());
46        return "redirect:/todo-item/{id}";
47    }
48
49    private void addFeedbackMessage(RedirectAttributes attributes,
50                                    String messageCode,
51                                    Locale currentLocale,
52                                    Object... messageParameters) {
53        String feedbackMessage = messageSource.getMessage(messageCode,
54                messageParameters,
55                currentLocale
56        );
57        attributes.addFlashAttribute("feedbackMessage", feedbackMessage);
58    }
59}

The CreateTodoItemFormDTO class contains the information of the form object that’s used to create new todo items. It also declares the validation rules which are used to validate the form object. The source code of the CreateTodoItemFormDTO class looks as follows:

 1import javax.validation.constraints.NotBlank;
 2import javax.validation.constraints.Size;
 3
 4public class CreateTodoItemFormDTO {
 5
 6    @Size(max = 1000)
 7    private String description;
 8
 9    @NotBlank
10    @Size(max = 100)
11    private String title;
12
13    //Getters and setters are omitted
14}

Next, we will learn how we can write assertions for the response returned by the system under test.

Writing Assertions for the Response Returned by the System Under Test

Before we can write unit tests for a Spring MVC controller which submits a form, we have to learn how we can write assertions for the response returned by the system under test. When we want to write assertions for the response returned by the tested Spring MVC controller, we have to use these static methods of the MockMvcResultMatchers class:

  • The status() method returns a StatusResultMatchers object which allows us to write assertions for the returned HTTP status.
  • The view() method returns a ViewResultMatchers object which allows us to write assertions for the rendered view.
  • The model() method returns a ModelResultMatchers object which allows us to write assertions for the Spring MVC model.
  • The flash() method returns a FlashAttributeResultMatchers object which allows us to write assertions for the flash attributes (aka flash messages) shown to the user.

We are now ready to write unit tests for the system under test. Let’s start by writing a new request builder method.

Writing a New Request Builder Method

Because we want to remove duplicate code from our test class, we have to create and send HTTP requests to the system under test by using a so called request builder class. In other words, before we can write unit tests for the system under test, we have write to a request builder method which sends HTTP requests to the system under test. We can write this request builder method by following these steps:

First, we have to add a new method called create() to our request builder class. This method takes a CreateTodoItemFormDTO object as a method parameter and returns a ResultActions object.

After we have added this method to our request builder class, its source code looks as follows:

 1import org.springframework.test.web.servlet.MockMvc;
 2import org.springframework.test.web.servlet.ResultActions;
 3
 4class TodoItemRequestBuilder {
 5
 6    private final MockMvc mockMvc;
 7
 8    TodoItemRequestBuilder(MockMvc mockMvc) {
 9        this.mockMvc = mockMvc;
10    }
11
12    ResultActions create(CreateTodoItemFormDTO formObject) throws Exception {
13
14    }
15}

Second, we have to implement the create() method by following these steps:

  1. Send a POST request to the path: ‘/todo-item’ by invoking the perform() method of the MockMvc class. Remember to return the ResultActions object that’s returned by the perform() method.
  2. Configure the field values of the submitted form by using the param() method of the MockHttpServletRequestBuilder class.

After we have implemented the create() method, the source code of our request builder class looks as follows:

 1import org.springframework.test.web.servlet.MockMvc;
 2import org.springframework.test.web.servlet.ResultActions;
 3
 4import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 5
 6class TodoItemRequestBuilder {
 7
 8    private final MockMvc mockMvc;
 9
10    TodoItemRequestBuilder(MockMvc mockMvc) {
11        this.mockMvc = mockMvc;
12    }
13
14    ResultActions create(CreateTodoItemFormDTO formObject) throws Exception {
15        return mockMvc.perform(post("/todo-item")
16                .param("description", formObject.getDescription())
17                .param("title", formObject.getTitle())
18        );
19    }
20}

Next, we will learn to write unit tests for the system under test.

Writing Unit Tests for the System Under Test

When we want to write unit tests for the system under test, we have to follow these steps:

First, we have to add the required class hierarchy to our test class. Because we are writing unit tests, we can create this class hierarchy by following these steps:

  1. Add an inner class called SubmitFormThatCreatesNewTodoItems to our test class. This inner class contains the test methods which ensure that the system under test is working as expected.
  2. Add an inner class called WhenValidationFails to the SubmitFormThatCreatesNewTodoItems class. This inner class contains the test methods which ensure that the system under test is working as expected when validation fails.
  3. Add an inner class called WhenValidationIsSuccessful to the SubmitFormThatCreatesNewTodoItems class. This inner class contains the test methods which ensure that the system under test is working as expected when validation is successful.

After we have added the required class hierarchy to our test class, its source code looks as follows:

 1import org.junit.jupiter.api.BeforeEach;
 2import org.junit.jupiter.api.DisplayName;
 3import org.junit.jupiter.api.Nested;
 4import org.springframework.context.support.StaticMessageSource;
 5import org.springframework.test.web.servlet.MockMvc;
 6import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 7
 8import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.exceptionResolver;
 9import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.fixedLocaleResolver;
10import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.jspViewResolver;
11import static org.mockito.Mockito.mock;
12
13public class TodoItemCrudControllerTest {
14
15    private StaticMessageSource messageSource = new StaticMessageSource();
16    private TodoItemRequestBuilder requestBuilder;
17    private TodoItemCrudService service;
18
19    @BeforeEach
20    void configureSystemUnderTest() {
21        service = mock(TodoItemCrudService.class);
22
23        MockMvc mockMvc = MockMvcBuilders
24                .standaloneSetup(new TodoItemCrudController(messageSource, service))
25                .setHandlerExceptionResolvers(exceptionResolver())
26                .setLocaleResolver(fixedLocaleResolver())
27                .setViewResolvers(jspViewResolver())
28                .build();
29        requestBuilder = new TodoItemRequestBuilder(mockMvc);
30    }
31
32    @Nested
33    @DisplayName("Submit the create todo item form")
34    class SubmitCreateTodoItemForm {
35
36        @Nested
37        @DisplayName("When validation fails")
38        class WhenValidationFails {
39
40        }
41
42        @Nested
43        @DisplayName("When validation is successful")
44        class WhenValidationIsSuccessful {
45
46        }
47    }
48}

Second, we have to make the following changes to the SubmitFormThatCreatesNewTodoItems class:

  1. Declare the constants which are used by the test methods found from the WhenValidationFails and WhenValidationIsSuccessful inner classes.
  2. Add a private field to the SubmitFormThatCreatesNewTodoItems class. This field is called formObject and it contains the information of the created todo item.

After we have made these changes to the `` class, the source code of our test class looks as follows:

 1import org.junit.jupiter.api.BeforeEach;
 2import org.junit.jupiter.api.DisplayName;
 3import org.junit.jupiter.api.Nested;
 4import org.springframework.context.support.StaticMessageSource;
 5import org.springframework.test.web.servlet.MockMvc;
 6import org.springframework.test.web.servlet.setup.MockMvcBuilders;
 7
 8import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.exceptionResolver;
 9import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.fixedLocaleResolver;
10import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.jspViewResolver;
11import static org.mockito.Mockito.mock;
12
13public class TodoItemCrudControllerTest {
14
15    private StaticMessageSource messageSource = new StaticMessageSource();
16    private TodoItemRequestBuilder requestBuilder;
17    private TodoItemCrudService service;
18
19    @BeforeEach
20    void configureSystemUnderTest() {
21        service = mock(TodoItemCrudService.class);
22
23        MockMvc mockMvc = MockMvcBuilders
24                .standaloneSetup(new TodoItemCrudController(messageSource, service))
25                .setHandlerExceptionResolvers(exceptionResolver())
26                .setLocaleResolver(fixedLocaleResolver())
27                .setViewResolvers(jspViewResolver())
28                .build();
29        requestBuilder = new TodoItemRequestBuilder(mockMvc);
30    }
31
32    @Nested
33    @DisplayName("Submit the create todo item form")
34    class SubmitCreateTodoItemForm {
35
36        private static final String FORM_OBJECT_ALIAS = "todoItem";
37        private static final int MAX_LENGTH_DESCRIPTION = 1000;
38        private static final int MAX_LENGTH_TITLE = 100;
39
40        private CreateTodoItemFormDTO formObject;
41
42        @Nested
43        @DisplayName("When validation fails")
44        class WhenValidationFails {
45
46        }
47
48        @Nested
49        @DisplayName("When validation is successful")
50        class WhenValidationIsSuccessful {
51
52        }
53    }
54}

Third, we have to ensure that the system under test is working as expected when validation fails. We can write the required test methods by following these steps:

  1. Add the required constants to the WhenValidationFails class.
  2. Add a new setup method to the WhenValidationFails class and ensure that it’s run before a test method is run. When we implement this method, we must the create the form object that’s used by our test methods. Because we want ensure that the system under test is working as expected when an empty form is submitted, we have to create a new CreateTodoItemFormDTO object that has an empty title and description.
  3. Ensure that the system under test returns the HTTP status code 200.
  4. Verify that the system under test renders the form view.
  5. Ensure that the system under test displays an empty create todo item form.
  6. Verify that the system under test displays one validation error.
  7. Ensure that the system under test displays a validation error about an empty title.
  8. Verify that the system under test doesn’t create a new todo item.

After we have written the required test methods, the source code of our test class looks as follows:

  1import org.junit.jupiter.api.BeforeEach;
  2import org.junit.jupiter.api.DisplayName;
  3import org.junit.jupiter.api.Nested;
  4import org.junit.jupiter.api.Test;
  5import org.springframework.context.support.StaticMessageSource;
  6import org.springframework.test.web.servlet.MockMvc;
  7import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  8
  9import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.exceptionResolver;
 10import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.fixedLocaleResolver;
 11import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.jspViewResolver;
 12import static org.hamcrest.Matchers.allOf;
 13import static org.hamcrest.Matchers.emptyString;
 14import static org.hamcrest.Matchers.hasProperty;
 15import static org.hamcrest.Matchers.is;
 16import static org.mockito.ArgumentMatchers.any;
 17import static org.mockito.Mockito.mock;
 18import static org.mockito.Mockito.never;
 19import static org.mockito.Mockito.verify;
 20import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
 21import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 22import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
 23
 24public class TodoItemCrudControllerTest {
 25
 26    private StaticMessageSource messageSource = new StaticMessageSource();
 27    private TodoItemRequestBuilder requestBuilder;
 28    private TodoItemCrudService service;
 29
 30    @BeforeEach
 31    void configureSystemUnderTest() {
 32        service = mock(TodoItemCrudService.class);
 33
 34        MockMvc mockMvc = MockMvcBuilders
 35                .standaloneSetup(new TodoItemCrudController(messageSource, service))
 36                .setHandlerExceptionResolvers(exceptionResolver())
 37                .setLocaleResolver(fixedLocaleResolver())
 38                .setViewResolvers(jspViewResolver())
 39                .build();
 40        requestBuilder = new TodoItemRequestBuilder(mockMvc);
 41    }
 42
 43    @Nested
 44    @DisplayName("Submit the create todo item form")
 45    class SubmitCreateTodoItemForm {
 46
 47        private static final String FORM_OBJECT_ALIAS = "todoItem";
 48        private static final int MAX_LENGTH_DESCRIPTION = 1000;
 49        private static final int MAX_LENGTH_TITLE = 100;
 50
 51        private CreateTodoItemFormDTO formObject;
 52
 53        @Nested
 54        @DisplayName("When validation fails")
 55        class WhenValidationFails {
 56
 57            private static final String FORM_FIELD_NAME_DESCRIPTION = "description";
 58            private static final String FORM_FIELD_NAME_TITLE = "title";
 59
 60            private static final String VALIDATION_ERROR_NOT_BLANK = "NotBlank";
 61
 62            private static final String VIEW_NAME_FORM_VIEW = "todo-item/create";
 63
 64            @BeforeEach
 65            void createFormObject() {
 66                formObject = new CreateTodoItemFormDTO();
 67                formObject.setDescription("");
 68                formObject.setTitle("");
 69            }
 70
 71            @Test
 72            @DisplayName("Should return the HTTP status code OK (200)")
 73            void shouldReturnHttpStatusCodeOk() throws Exception {
 74                requestBuilder.create(formObject)
 75                        .andExpect(status().isOk());
 76            }
 77
 78            @Test
 79            @DisplayName("Should render the form view")
 80            void shouldRenderFormView() throws Exception {
 81                requestBuilder.create(formObject)
 82                        .andExpect(view().name(VIEW_NAME_FORM_VIEW));
 83            }
 84
 85            @Test
 86            @DisplayName("Should display an empty create todo item form")
 87            void shouldDisplayEmptyCreateTodoItemForm() throws Exception {
 88                requestBuilder.create(formObject)
 89                        .andExpect(model().attribute(FORM_OBJECT_ALIAS, allOf(
 90                                hasProperty(
 91                                        FORM_FIELD_NAME_DESCRIPTION,
 92                                        is(emptyString())
 93                                ),
 94                                hasProperty(
 95                                        FORM_FIELD_NAME_TITLE,
 96                                        is(emptyString())
 97                                )
 98                        )));
 99            }
100
101            @Test
102            @DisplayName("Should display one validation error")
103            void shouldDisplayOneValidationError() throws Exception {
104                requestBuilder.create(formObject)
105                        .andExpect(model().attributeErrorCount(FORM_OBJECT_ALIAS, 1));
106            }
107
108            @Test
109            @DisplayName("Should display a validation error about empty title")
110            void shouldDisplayValidationErrorAboutEmptyTitle() throws Exception {
111                requestBuilder.create(formObject)
112                        .andExpect(model().attributeHasFieldErrorCode(
113                                FORM_OBJECT_ALIAS,
114                                FORM_FIELD_NAME_TITLE,
115                                VALIDATION_ERROR_NOT_BLANK
116                        ));
117            }
118
119            @Test
120            @DisplayName("Shouldn't create a new todo item")
121            void shouldNotCreateNewTodoItem() throws Exception {
122                requestBuilder.create(formObject);
123                verify(service, never()).create(any());
124            }
125        }
126
127        //The other inner class is omitted
128    }
129}

The validation of the form object will fail if:

  • The title of the created todo item is null
  • The title of the created todo item is empty.
  • The title of the created todo item contains only whitespace characters.
  • The title or description of the created todo item is too long.

This blog post covers only one scenario because I didn’t want to repeat myself. If you want to take a look at the tests which ensure that the system under test is working as expected in all possible scenarios, you should take a look at the example application of this blog post.

Fourth, we have to ensure that the system under test is working as expected when validation is successful. We can write the required test methods by following these steps:

  1. Add the required constants to the WhenValidationIsSuccessful class.
  2. Add a new setup method to the WhenValidationIsSuccessful class and ensure that it’s run before a test method is run. When we implement this method, we must:
  • Create a form object that has a valid title and description.
  • Configure the feedback message that’s shown to the user.
  • Ensure that the create() method of the TodoItemCrudService class returns the information of the created todo item.
  1. Verify that the system under test returns the HTTP status code 302.
  2. Ensure that the system under test redirects the HTTP request to the view todo item view.
  3. Verify that the system under test displays the correct flash message.
  4. Ensure that the system under test creates a new todo item with the correct description.
  5. Verify that the system under test creates a new todo item with the correct title.

After we have written the required test methods, the source code of our test class looks as follows:

  1import org.junit.jupiter.api.BeforeEach;
  2import org.junit.jupiter.api.DisplayName;
  3import org.junit.jupiter.api.Nested;
  4import org.junit.jupiter.api.Test;
  5import org.springframework.context.support.StaticMessageSource;
  6import org.springframework.test.web.servlet.MockMvc;
  7import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  8
  9import static info.solidsoft.mockito.java8.AssertionMatcher.assertArg;
 10import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.exceptionResolver;
 11import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.fixedLocaleResolver;
 12import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.jspViewResolver;
 13import static net.petrikainulainen.springmvctest.junit5.web.WebTestUtil.createStringWithLength;
 14import static org.assertj.core.api.Assertions.assertThat;
 15import static org.hamcrest.Matchers.equalTo;
 16import static org.mockito.ArgumentMatchers.any;
 17import static org.mockito.BDDMockito.given;
 18import static org.mockito.Mockito.mock;
 19import static org.mockito.Mockito.times;
 20import static org.mockito.Mockito.verify;
 21import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.flash;
 22import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
 23import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 24import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
 25
 26public class TodoItemCrudControllerTest {
 27
 28    private StaticMessageSource messageSource = new StaticMessageSource();
 29    private TodoItemRequestBuilder requestBuilder;
 30    private TodoItemCrudService service;
 31
 32    @BeforeEach
 33    void configureSystemUnderTest() {
 34        service = mock(TodoItemCrudService.class);
 35
 36        MockMvc mockMvc = MockMvcBuilders
 37                .standaloneSetup(new TodoItemCrudController(messageSource, service))
 38                .setHandlerExceptionResolvers(exceptionResolver())
 39                .setLocaleResolver(fixedLocaleResolver())
 40                .setViewResolvers(jspViewResolver())
 41                .build();
 42        requestBuilder = new TodoItemRequestBuilder(mockMvc);
 43    }
 44
 45    @Nested
 46    @DisplayName("Submit the create todo item form")
 47    class SubmitCreateTodoItemForm {
 48
 49        private static final String FORM_OBJECT_ALIAS = "todoItem";
 50        private static final int MAX_LENGTH_DESCRIPTION = 1000;
 51        private static final int MAX_LENGTH_TITLE = 100;
 52
 53        private CreateTodoItemFormDTO formObject;
 54
 55        //The other inner class is omitted
 56
 57        @Nested
 58        @DisplayName("When validation is successful")
 59        class WhenValidationIsSuccessful {
 60
 61            private static final String FEEDBACK_MESSAGE = "A new todo item was created";
 62            private static final String FEEDBACK_MESSAGE_KEY = "feedback.message.todoItem.created";
 63
 64            private static final String FLASH_ATTRIBUTE_KEY_FEEDBACK_MESSAGE = "feedbackMessage";
 65
 66            private static final String MODEL_ATTRIBUTE_NAME_ID = "id";
 67            private static final String VIEW_NAME_VIEW_TODO_ITEM_VIEW = "redirect:/todo-item/{id}";
 68
 69            private static final Long ID = 1L;
 70            private static final String DESCRIPTION = createStringWithLength(MAX_LENGTH_DESCRIPTION);
 71            private static final String TITLE = createStringWithLength(MAX_LENGTH_TITLE);
 72
 73            @BeforeEach
 74            void configureSystemUnderTest() {
 75                formObject = createFormObject();
 76                configureFeedbackMessage();
 77                returnCreatedTodoItem();
 78            }
 79
 80            private CreateTodoItemFormDTO createFormObject() {
 81                CreateTodoItemFormDTO formObject = new CreateTodoItemFormDTO();
 82                formObject.setDescription(DESCRIPTION);
 83                formObject.setTitle(TITLE);
 84                return formObject;
 85            }
 86
 87            private void configureFeedbackMessage() {
 88                messageSource.addMessage(
 89                        FEEDBACK_MESSAGE_KEY,
 90                        WebTestConfig.LOCALE,
 91                        FEEDBACK_MESSAGE
 92                );
 93            }
 94
 95            private void returnCreatedTodoItem() {
 96                TodoItemDTO created = new TodoItemDTO();
 97                created.setId(ID);
 98
 99                given(service.create(any())).willReturn(created);
100            }
101
102            @Test
103            @DisplayName("Should return the HTTP status code found (302)")
104            void shouldReturnHttpStatusCodeFound() throws Exception {
105                requestBuilder.create(formObject)
106                        .andExpect(status().isFound());
107            }
108
109            @Test
110            @DisplayName("Should redirect the HTTP request to the view todo item view")
111            void shouldRedirectHttpRequestToViewTodoItemView() throws Exception {
112                requestBuilder.create(formObject)
113                        .andExpect(view().name(VIEW_NAME_VIEW_TODO_ITEM_VIEW))
114                        .andExpect(model().attribute(
115                                MODEL_ATTRIBUTE_NAME_ID, equalTo(ID.toString())));
116            }
117
118            @Test
119            @DisplayName("Should display the correct flash message")
120            void shouldDisplayCorrectFlashMessage() throws Exception {
121                requestBuilder.create(formObject)
122                        .andExpect(flash().attribute(
123                                FLASH_ATTRIBUTE_KEY_FEEDBACK_MESSAGE,
124                                equalTo(FEEDBACK_MESSAGE)
125                        ));
126            }
127
128            @Test
129            @DisplayName("Should create a new todo item with the correct description")
130            void shouldCreateNewTodoItemWithCorrectDescription() throws Exception {
131                requestBuilder.create(formObject);
132
133                verify(service, times(1)).create(assertArg(
134                        todoItem -> assertThat(todoItem.getDescription())
135                                .isEqualTo(DESCRIPTION)
136                ));
137            }
138
139            @Test
140            @DisplayName("Should create a new todo item with the correct title")
141            void shouldCreateNewTodoItemWithCorrectTitle() throws Exception {
142                requestBuilder.create(formObject);
143
144                verify(service, times(1)).create(assertArg(
145                        todoItem -> assertThat(todoItem.getTitle())
146                                .isEqualTo(TITLE)
147                ));
148            }
149        }
150    }
151}

We can now write unit tests for a controller method that submits a form. Let’s summarize what we learned from this blog post.

Summary

This blog post has taught us six things:

  • We can configure the field values of the submitted form by using the param() method of the MockHttpServletRequestBuilder class.
  • When we have to ensure that the system under test displays X validation errors, we have to invoke the attributeErrorCount() method of the ModelResultMatchers class.
  • When we have to verify that the system under test displays the correct validation error, we have to use attributeHasFieldErrorCode() method of the ModelResultMatchers class.
  • When we have to ensure that fields of the rendered form contain correct information, we have to invoke the attribute() method of the ModelResultMatchers class.
  • When we have to verify that the HTTP request is redirected to the correct path, we have to use the name() method of the ViewResultMatchers class.
  • When we have to ensure that the system under test displays the correct flash message to the user, we have to invoke the attribute() method of the FlashAttributeResultMatchers class.

P.S. You can get the example application of this blog post from Github.