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.
This blog post assumes that:
- You are familiar with JUnit 5
- You are familiar with the Spring MVC Test Framework
- You understand how you can configure the system under test
- You know how you can send HTTP requests to the system under test
- You can write unit tests for a Spring MVC controller which renders the information of a single item or renders a list
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:
- If the submitted form has validation errors, return the name of the form view (’todo-item/create’).
- Save the created todo item to the database by invoking the
create()method of theTodoItemCrudServiceclass. - 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.
- 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 aStatusResultMatchersobject which allows us to write assertions for the returned HTTP status. - The
view()method returns aViewResultMatchersobject which allows us to write assertions for the rendered view. - The
model()method returns aModelResultMatchersobject which allows us to write assertions for the Spring MVC model. - The
flash()method returns aFlashAttributeResultMatchersobject which allows us to write assertions for the flash attributes (aka flash messages) shown to the user.
Additional Reading:
- The Javadoc of the
FlashAttributeResultMatchersclass - The Javadoc of the
ModelResultMatchersclass - [The Javadoc of the
StatusResultMatchersclass - The Javadoc of the
ViewResultMatchersclass](https://docs.spring.io/spring-framework/docs/5.3.x/javadoc-api/org/springframework/test/web/servlet/result/StatusResultMatchers.html)
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:
- Send a
POSTrequest to the path: ‘/todo-item’ by invoking theperform()method of theMockMvcclass. Remember to return theResultActionsobject that’s returned by theperform()method. - Configure the field values of the submitted form by using the
param()method of theMockHttpServletRequestBuilderclass.
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}
Additional Reading:
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:
- Add an inner class called
SubmitFormThatCreatesNewTodoItemsto our test class. This inner class contains the test methods which ensure that the system under test is working as expected. - Add an inner class called
WhenValidationFailsto theSubmitFormThatCreatesNewTodoItemsclass. This inner class contains the test methods which ensure that the system under test is working as expected when validation fails. - Add an inner class called
WhenValidationIsSuccessfulto theSubmitFormThatCreatesNewTodoItemsclass. 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:
- Declare the constants which are used by the test methods found from the
WhenValidationFailsandWhenValidationIsSuccessfulinner classes. - Add a
privatefield to theSubmitFormThatCreatesNewTodoItemsclass. This field is calledformObjectand 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:
- Add the required constants to the
WhenValidationFailsclass. - Add a new setup method to the
WhenValidationFailsclass 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 newCreateTodoItemFormDTOobject that has an empty title and description. - Ensure that the system under test returns the HTTP status code 200.
- Verify that the system under test renders the form view.
- Ensure that the system under test displays an empty create todo item form.
- Verify that the system under test displays one validation error.
- Ensure that the system under test displays a validation error about an empty title.
- 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:
- Add the required constants to the
WhenValidationIsSuccessfulclass. - Add a new setup method to the
WhenValidationIsSuccessfulclass 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 theTodoItemCrudServiceclass returns the information of the created todo item.
- Verify that the system under test returns the HTTP status code 302.
- Ensure that the system under test redirects the HTTP request to the view todo item view.
- Verify that the system under test displays the correct flash message.
- Ensure that the system under test creates a new todo item with the correct description.
- 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}
Additional Reading:
- The Javadoc of the
FlashAttributeResultMatchersclass - The Javadoc of the
ModelResultMatchersclass - [The Javadoc of the
StatusResultMatchersclass - The Javadoc of the
ViewResultMatchersclass](https://docs.spring.io/spring-framework/docs/5.3.x/javadoc-api/org/springframework/test/web/servlet/result/StatusResultMatchers.html) - The Javadoc of the
Matchersclass - The Javadoc of the
Mockitoclass - The README of the mockito-java8 library
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 theMockHttpServletRequestBuilderclass. - When we have to ensure that the system under test displays X validation errors, we have to invoke the
attributeErrorCount()method of theModelResultMatchersclass. - When we have to verify that the system under test displays the correct validation error, we have to use
attributeHasFieldErrorCode()method of theModelResultMatchersclass. - When we have to ensure that fields of the rendered form contain correct information, we have to invoke the
attribute()method of theModelResultMatchersclass. - When we have to verify that the HTTP request is redirected to the correct path, we have to use the
name()method of theViewResultMatchersclass. - 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 theFlashAttributeResultMatchersclass.
P.S. You can get the example application of this blog post from Github.