Java >> Java Tutorial >  >> Tag >> Spring

Schreiben von Komponententests für Spring MVC-Controller:Formulare

Im vorherigen Teil meines Spring MVC Test-Tutorials wurde beschrieben, wie wir Komponententests für einen Spring MVC-Controller schreiben können, der eine Liste rendert. Dieser Blogbeitrag enthält weitere Informationen zum Schreiben von Komponententests für Spring MVC-Controller, die Daten in die verwendete Datenbank einfügen. Genauer gesagt beschreibt dieser Blogbeitrag, wie wir Komponententests für einen Spring MVC-Controller schreiben können, der ein Formular sendet.

Nachdem wir diesen Blogbeitrag fertiggestellt haben, werden wir:

  • Wissen, wie wir ein Formular mit dem Spring MVC Test Framework senden können.
  • Verstehen Sie, wie wir sicherstellen können, dass das zu testende System die richtigen Validierungsfehler anzeigt, wenn wir ein Formular mit ungültigen Informationen senden.
  • Wissen, wie wir sicherstellen können, dass die Felder des übermittelten Formulars die richtigen Informationen enthalten, wenn die Validierung fehlschlägt.
  • Kann überprüfen, ob die HTTP-Anfrage auf den richtigen Pfad umgeleitet wird.
  • Wissen, wie wir sicherstellen können, dass das zu testende System dem Benutzer die richtige Flash-Meldung anzeigt.

Fangen wir an.

Einführung in das zu testende System

Wir müssen Komponententests für eine Controller-Methode schreiben, die POST verarbeitet Anfragen werden an den Pfad gesendet:'/todo-items'. Diese Methode erstellt ein neues ToDo-Element und leitet den Benutzer zur ToDo-Elementansicht um. Wenn die Validierung fehlschlägt, gibt diese Controller-Methode den HTTP-Statuscode 200 zurück und rendert die Formularansicht.

Die getestete Controller-Methode heißt create() und es wird wie folgt implementiert:

  1. Wenn das übermittelte Formular Validierungsfehler aufweist, geben Sie den Namen der Formularansicht zurück („todo-item/create“).
  2. Speichern Sie das erstellte Aufgabenelement in der Datenbank, indem Sie create() aufrufen Methode des TodoItemCrudService Klasse.
  3. Erstellen Sie eine Feedback-Nachricht, die besagt, dass ein neues Aufgabenelement erstellt wurde, und stellen Sie sicher, dass diese Nachricht angezeigt wird, wenn die nächste Ansicht gerendert wird.
  4. Leiten Sie die HTTP-Anforderung an die Ansicht weiter, die die Informationen des erstellten ToDo-Elements darstellt.

Der Quellcode der getesteten Controller-Methode sieht wie folgt aus:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.validation.Valid;
import java.util.Locale;

@Controller
@RequestMapping("/todo-item")
public class TodoItemCrudController {

    private final MessageSource messageSource;
    private final TodoItemCrudService service;

    @Autowired
    public TodoItemCrudController(MessageSource messageSource, 
                                  TodoItemCrudService service) {
        this.messageSource = messageSource;
        this.service = service;
    }

    @PostMapping
    public String create(@Valid @ModelAttribute("todoItem") CreateTodoItemFormDTO form,
                         BindingResult bindingResult,
                         RedirectAttributes redirectAttributes,
                         Locale currentLocale) {
        if (bindingResult.hasErrors()) {
            return "todo-item/create";
        }

        TodoItemDTO created = service.create(form);

        addFeedbackMessage(
                redirectAttributes,
                "feedback.message.todoItem.created",
                currentLocale,
                created.getTitle()
        );

        redirectAttributes.addAttribute("id", created.getId());
        return "redirect:/todo-item/{id}";
    }

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

Die CreateTodoItemFormDTO -Klasse enthält die Informationen des Formularobjekts, das zum Erstellen neuer Aufgabenelemente verwendet wird. Es deklariert auch die Validierungsregeln, die verwendet werden, um das Formularobjekt zu validieren. Der Quellcode von CreateTodoItemFormDTO Klasse sieht wie folgt aus:

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

public class CreateTodoItemFormDTO {

    @Size(max = 1000)
    private String description;

    @NotBlank
    @Size(max = 100)
    private String title;

    //Getters and setters are omitted
}

Als Nächstes lernen wir, wie wir Assertionen für die vom zu testenden System zurückgegebene Antwort schreiben können.

Schreiben von Zusicherungen für die Antwort, die vom zu testenden System zurückgegeben wird

Bevor wir Komponententests für einen Spring MVC-Controller schreiben können, der ein Formular sendet, müssen wir lernen, wie wir Zusicherungen für die vom zu testenden System zurückgegebene Antwort schreiben können. Wenn wir Zusicherungen für die vom getesteten Spring MVC-Controller zurückgegebene Antwort schreiben möchten, müssen wir diese static verwenden Methoden der MockMvcResultMatchers Klasse:

  • Der status() Methode gibt ein StatusResultMatchers zurück Objekt, das es uns erlaubt, Assertionen für den zurückgegebenen HTTP-Status zu schreiben.
  • Der view() Methode gibt ein ViewResultMatchers zurück -Objekt, das es uns ermöglicht, Behauptungen für die gerenderte Ansicht zu schreiben.
  • Die model() Methode gibt einen ModelResultMatchers zurück -Objekt, mit dem wir Behauptungen für das Spring-MVC-Modell schreiben können.
  • Der flash() Methode gibt einen FlashAttributeResultMatchers zurück -Objekt, das es uns erlaubt, Behauptungen für die Flash-Attribute (auch bekannt als Flash-Nachrichten) zu schreiben, die dem Benutzer angezeigt werden.

Wir sind jetzt bereit, Komponententests für das zu testende System zu schreiben. Beginnen wir mit dem Schreiben einer neuen Request-Builder-Methode.

Schreiben einer neuen Request Builder-Methode

Da wir doppelten Code aus unserer Testklasse entfernen möchten, müssen wir HTTP-Anforderungen erstellen und an das zu testende System senden, indem wir eine sogenannte Request-Builder-Klasse verwenden. Mit anderen Worten, bevor wir Komponententests für das zu testende System schreiben können, müssen wir in eine Request-Builder-Methode schreiben, die HTTP-Anforderungen an das zu testende System sendet. Wir können diese Request-Builder-Methode schreiben, indem wir diesen Schritten folgen:

Zuerst , müssen wir eine neue Methode namens create() hinzufügen zu unserer Request-Builder-Klasse. Diese Methode benötigt CreateTodoItemFormDTO Objekt als Methodenparameter und gibt ein ResultActions zurück Objekt.

Nachdem wir diese Methode zu unserer Request-Builder-Klasse hinzugefügt haben, sieht ihr Quellcode wie folgt aus:

import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

class TodoItemRequestBuilder {

    private final MockMvc mockMvc;

    TodoItemRequestBuilder(MockMvc mockMvc) {
        this.mockMvc = mockMvc;
    }

    ResultActions create(CreateTodoItemFormDTO formObject) throws Exception {
        
    }
}

Zweite , müssen wir den create() implementieren Methode, indem Sie diesen Schritten folgen:

  1. Senden Sie einen POST Anfrage an den Pfad:'/todo-item' durch Aufrufen des perform() Methode des MockMvc Klasse. Denken Sie daran, ResultActions zurückzugeben Objekt, das von perform() zurückgegeben wird Methode.
  2. Konfigurieren Sie die Feldwerte des übermittelten Formulars mit param() Methode des MockHttpServletRequestBuilder Klasse.

Nachdem wir den create() implementiert haben -Methode sieht der Quellcode unserer Request-Builder-Klasse wie folgt aus:

import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;

class TodoItemRequestBuilder {

    private final MockMvc mockMvc;

    TodoItemRequestBuilder(MockMvc mockMvc) {
        this.mockMvc = mockMvc;
    }
    
    ResultActions create(CreateTodoItemFormDTO formObject) throws Exception {
        return mockMvc.perform(post("/todo-item")
                .param("description", formObject.getDescription())
                .param("title", formObject.getTitle())
        );
    }
}

Als Nächstes lernen wir, Unit-Tests für das zu testende System zu schreiben.

Einheitentests für das zu testende System schreiben

Wenn wir Unit-Tests für das zu testende System schreiben wollen, müssen wir diese Schritte befolgen:

Zuerst , müssen wir unserer Testklasse die erforderliche Klassenhierarchie hinzufügen. Da wir Komponententests schreiben, können wir diese Klassenhierarchie folgendermaßen erstellen:

  1. Fügen Sie eine innere Klasse namens SubmitFormThatCreatesNewTodoItems hinzu zu unserer Testklasse. Diese innere Klasse enthält die Testmethoden, die sicherstellen, dass das zu testende System wie erwartet funktioniert.
  2. Fügen Sie eine innere Klasse namens WhenValidationFails hinzu zum SubmitFormThatCreatesNewTodoItems Klasse. Diese innere Klasse enthält die Testmethoden, die sicherstellen, dass das zu testende System wie erwartet funktioniert, wenn die Validierung fehlschlägt.
  3. Fügen Sie eine innere Klasse namens WhenValidationIsSuccessful hinzu zum SubmitFormThatCreatesNewTodoItems Klasse. Diese innere Klasse enthält die Testmethoden, die sicherstellen, dass das zu testende System bei erfolgreicher Validierung wie erwartet funktioniert.

Nachdem wir unserer Testklasse die erforderliche Klassenhierarchie hinzugefügt haben, sieht ihr Quellcode wie folgt aus:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.exceptionResolver;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.fixedLocaleResolver;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.jspViewResolver;
import static org.mockito.Mockito.mock;

public class TodoItemCrudControllerTest {

    private StaticMessageSource messageSource = new StaticMessageSource();
    private TodoItemRequestBuilder requestBuilder;
    private TodoItemCrudService service;

    @BeforeEach
    void configureSystemUnderTest() {
        service = mock(TodoItemCrudService.class);

        MockMvc mockMvc = MockMvcBuilders
                .standaloneSetup(new TodoItemCrudController(messageSource, service))
                .setHandlerExceptionResolvers(exceptionResolver())
                .setLocaleResolver(fixedLocaleResolver())
                .setViewResolvers(jspViewResolver())
                .build();
        requestBuilder = new TodoItemRequestBuilder(mockMvc);
    }

    @Nested
    @DisplayName("Submit the create todo item form")
    class SubmitCreateTodoItemForm {

        @Nested
        @DisplayName("When validation fails")
        class WhenValidationFails {

        }

        @Nested
        @DisplayName("When validation is successful")
        class WhenValidationIsSuccessful {

        }
    }
}

Zweite , müssen wir die folgenden Änderungen an SubmitFormThatCreatesNewTodoItems vornehmen Klasse:

  1. Deklarieren Sie die Konstanten, die von den in WhenValidationFails gefundenen Testmethoden verwendet werden und WhenValidationIsSuccessful innere Klassen.
  2. Fügen Sie einen private hinzu Feld zum SubmitFormThatCreatesNewTodoItems Klasse. Dieses Feld heißt formObject und es enthält die Informationen des erstellten ToDo-Elements.

Nachdem wir diese Änderungen am vorgenommen haben Klasse sieht der Quellcode unserer Testklasse wie folgt aus:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.exceptionResolver;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.fixedLocaleResolver;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.jspViewResolver;
import static org.mockito.Mockito.mock;

public class TodoItemCrudControllerTest {

    private StaticMessageSource messageSource = new StaticMessageSource();
    private TodoItemRequestBuilder requestBuilder;
    private TodoItemCrudService service;

    @BeforeEach
    void configureSystemUnderTest() {
        service = mock(TodoItemCrudService.class);

        MockMvc mockMvc = MockMvcBuilders
                .standaloneSetup(new TodoItemCrudController(messageSource, service))
                .setHandlerExceptionResolvers(exceptionResolver())
                .setLocaleResolver(fixedLocaleResolver())
                .setViewResolvers(jspViewResolver())
                .build();
        requestBuilder = new TodoItemRequestBuilder(mockMvc);
    }

    @Nested
    @DisplayName("Submit the create todo item form")
    class SubmitCreateTodoItemForm {

        private static final String FORM_OBJECT_ALIAS = "todoItem";
        private static final int MAX_LENGTH_DESCRIPTION = 1000;
        private static final int MAX_LENGTH_TITLE = 100;

        private CreateTodoItemFormDTO formObject;

        @Nested
        @DisplayName("When validation fails")
        class WhenValidationFails {

        }

        @Nested
        @DisplayName("When validation is successful")
        class WhenValidationIsSuccessful {

        }
    }
}

Dritter müssen wir sicherstellen, dass das zu testende System wie erwartet funktioniert, wenn die Validierung fehlschlägt. Wir können die erforderlichen Testmethoden schreiben, indem wir diesen Schritten folgen:

  1. Fügen Sie die erforderlichen Konstanten zu WhenValidationFails hinzu Klasse.
  2. Fügen Sie dem WhenValidationFails eine neue Einrichtungsmethode hinzu -Klasse und stellen Sie sicher, dass sie ausgeführt wird, bevor eine Testmethode ausgeführt wird. Wenn wir diese Methode implementieren, müssen wir das Formularobjekt erstellen, das von unseren Testmethoden verwendet wird. Da wir sicherstellen möchten, dass das zu testende System wie erwartet funktioniert, wenn ein leeres Formular gesendet wird, müssen wir einen neuen CreateTodoItemFormDTO erstellen Objekt, das einen leeren Titel und eine leere Beschreibung hat.
  3. Stellen Sie sicher, dass das zu testende System den HTTP-Statuscode 200 zurückgibt.
  4. Vergewissern Sie sich, dass das zu testende System die Formularansicht darstellt.
  5. Stellen Sie sicher, dass das zu testende System ein leeres Formular zum Erstellen von Aufgaben anzeigt.
  6. Stellen Sie sicher, dass das zu testende System einen Validierungsfehler anzeigt.
  7. Stellen Sie sicher, dass das zu testende System einen Validierungsfehler zu einem leeren Titel anzeigt.
  8. Stellen Sie sicher, dass das zu testende System kein neues Aufgabenelement erstellt.

Nachdem wir die erforderlichen Testmethoden geschrieben haben, sieht der Quellcode unserer Testklasse wie folgt aus:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.exceptionResolver;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.fixedLocaleResolver;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.jspViewResolver;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.emptyString;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

public class TodoItemCrudControllerTest {

    private StaticMessageSource messageSource = new StaticMessageSource();
    private TodoItemRequestBuilder requestBuilder;
    private TodoItemCrudService service;

    @BeforeEach
    void configureSystemUnderTest() {
        service = mock(TodoItemCrudService.class);

        MockMvc mockMvc = MockMvcBuilders
                .standaloneSetup(new TodoItemCrudController(messageSource, service))
                .setHandlerExceptionResolvers(exceptionResolver())
                .setLocaleResolver(fixedLocaleResolver())
                .setViewResolvers(jspViewResolver())
                .build();
        requestBuilder = new TodoItemRequestBuilder(mockMvc);
    }

    @Nested
    @DisplayName("Submit the create todo item form")
    class SubmitCreateTodoItemForm {

        private static final String FORM_OBJECT_ALIAS = "todoItem";
        private static final int MAX_LENGTH_DESCRIPTION = 1000;
        private static final int MAX_LENGTH_TITLE = 100;

        private CreateTodoItemFormDTO formObject;

        @Nested
        @DisplayName("When validation fails")
        class WhenValidationFails {

            private static final String FORM_FIELD_NAME_DESCRIPTION = "description";
            private static final String FORM_FIELD_NAME_TITLE = "title";

            private static final String VALIDATION_ERROR_NOT_BLANK = "NotBlank";

            private static final String VIEW_NAME_FORM_VIEW = "todo-item/create";

            @BeforeEach
            void createFormObject() {
                formObject = new CreateTodoItemFormDTO();
                formObject.setDescription("");
                formObject.setTitle("");
            }

            @Test
            @DisplayName("Should return the HTTP status code OK (200)")
            void shouldReturnHttpStatusCodeOk() throws Exception {
                requestBuilder.create(formObject)
                        .andExpect(status().isOk());
            }

            @Test
            @DisplayName("Should render the form view")
            void shouldRenderFormView() throws Exception {
                requestBuilder.create(formObject)
                        .andExpect(view().name(VIEW_NAME_FORM_VIEW));
            }

            @Test
            @DisplayName("Should display an empty create todo item form")
            void shouldDisplayEmptyCreateTodoItemForm() throws Exception {
                requestBuilder.create(formObject)
                        .andExpect(model().attribute(FORM_OBJECT_ALIAS, allOf(
                                hasProperty(
                                        FORM_FIELD_NAME_DESCRIPTION, 
                                        is(emptyString())
                                ),
                                hasProperty(
                                        FORM_FIELD_NAME_TITLE, 
                                        is(emptyString())
                                )
                        )));
            }

            @Test
            @DisplayName("Should display one validation error")
            void shouldDisplayOneValidationError() throws Exception {
                requestBuilder.create(formObject)
                        .andExpect(model().attributeErrorCount(FORM_OBJECT_ALIAS, 1));
            }

            @Test
            @DisplayName("Should display a validation error about empty title")
            void shouldDisplayValidationErrorAboutEmptyTitle() throws Exception {
                requestBuilder.create(formObject)
                        .andExpect(model().attributeHasFieldErrorCode(
                                FORM_OBJECT_ALIAS,
                                FORM_FIELD_NAME_TITLE,
                                VALIDATION_ERROR_NOT_BLANK
                        ));
            }

            @Test
            @DisplayName("Shouldn't create a new todo item")
            void shouldNotCreateNewTodoItem() throws Exception {
                requestBuilder.create(formObject);
                verify(service, never()).create(any());
            }
        }

        //The other inner class is omitted
    }
}

Vierter müssen wir sicherstellen, dass das zu testende System wie erwartet funktioniert, wenn die Validierung erfolgreich ist. Wir können die erforderlichen Testmethoden schreiben, indem wir diesen Schritten folgen:

  1. Fügen Sie die erforderlichen Konstanten zu WhenValidationIsSuccessful hinzu Klasse.
  2. Fügen Sie dem WhenValidationIsSuccessful eine neue Einrichtungsmethode hinzu -Klasse und stellen Sie sicher, dass sie ausgeführt wird, bevor eine Testmethode ausgeführt wird. Wenn wir diese Methode implementieren, müssen wir:
    • Erstellen Sie ein Formularobjekt mit gültigem Titel und gültiger Beschreibung.
    • Konfigurieren Sie die Feedback-Nachricht, die dem Benutzer angezeigt wird.
    • Stellen Sie sicher, dass create() Methode des TodoItemCrudService Die Klasse gibt die Informationen des erstellten ToDo-Elements zurück.
  3. Vergewissern Sie sich, dass das zu testende System den HTTP-Statuscode 302 zurückgibt.
  4. Stellen Sie sicher, dass das zu testende System die HTTP-Anfrage zur Ansicht Todo-Elemente umleitet.
  5. Vergewissern Sie sich, dass das zu testende System die richtige Flash-Meldung anzeigt.
  6. Stellen Sie sicher, dass das zu testende System ein neues Aufgabenelement mit der richtigen Beschreibung erstellt.
  7. Vergewissern Sie sich, dass das zu testende System ein neues Aufgabenelement mit dem richtigen Titel erstellt.

Nachdem wir die erforderlichen Testmethoden geschrieben haben, sieht der Quellcode unserer Testklasse wie folgt aus:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static info.solidsoft.mockito.java8.AssertionMatcher.assertArg;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.exceptionResolver;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.fixedLocaleResolver;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.jspViewResolver;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestUtil.createStringWithLength;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.flash;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

public class TodoItemCrudControllerTest {

    private StaticMessageSource messageSource = new StaticMessageSource();
    private TodoItemRequestBuilder requestBuilder;
    private TodoItemCrudService service;

    @BeforeEach
    void configureSystemUnderTest() {
        service = mock(TodoItemCrudService.class);

        MockMvc mockMvc = MockMvcBuilders
                .standaloneSetup(new TodoItemCrudController(messageSource, service))
                .setHandlerExceptionResolvers(exceptionResolver())
                .setLocaleResolver(fixedLocaleResolver())
                .setViewResolvers(jspViewResolver())
                .build();
        requestBuilder = new TodoItemRequestBuilder(mockMvc);
    }

    @Nested
    @DisplayName("Submit the create todo item form")
    class SubmitCreateTodoItemForm {

        private static final String FORM_OBJECT_ALIAS = "todoItem";
        private static final int MAX_LENGTH_DESCRIPTION = 1000;
        private static final int MAX_LENGTH_TITLE = 100;

        private CreateTodoItemFormDTO formObject;

        //The other inner class is omitted

        @Nested
        @DisplayName("When validation is successful")
        class WhenValidationIsSuccessful {

            private static final String FEEDBACK_MESSAGE = "A new todo item was created";
            private static final String FEEDBACK_MESSAGE_KEY = "feedback.message.todoItem.created";

            private static final String FLASH_ATTRIBUTE_KEY_FEEDBACK_MESSAGE = "feedbackMessage";

            private static final String MODEL_ATTRIBUTE_NAME_ID = "id";
            private static final String VIEW_NAME_VIEW_TODO_ITEM_VIEW = "redirect:/todo-item/{id}";

            private static final Long ID = 1L;
            private static final String DESCRIPTION = createStringWithLength(MAX_LENGTH_DESCRIPTION);
            private static final String TITLE = createStringWithLength(MAX_LENGTH_TITLE);

            @BeforeEach
            void configureSystemUnderTest() {
                formObject = createFormObject();
                configureFeedbackMessage();
                returnCreatedTodoItem();
            }

            private CreateTodoItemFormDTO createFormObject() {
                CreateTodoItemFormDTO formObject = new CreateTodoItemFormDTO();
                formObject.setDescription(DESCRIPTION);
                formObject.setTitle(TITLE);
                return formObject;
            }

            private void configureFeedbackMessage() {
                messageSource.addMessage(
                        FEEDBACK_MESSAGE_KEY,
                        WebTestConfig.LOCALE,
                        FEEDBACK_MESSAGE
                );
            }

            private void returnCreatedTodoItem() {
                TodoItemDTO created = new TodoItemDTO();
                created.setId(ID);

                given(service.create(any())).willReturn(created);
            }

            @Test
            @DisplayName("Should return the HTTP status code found (302)")
            void shouldReturnHttpStatusCodeFound() throws Exception {
                requestBuilder.create(formObject)
                        .andExpect(status().isFound());
            }

            @Test
            @DisplayName("Should redirect the HTTP request to the view todo item view")
            void shouldRedirectHttpRequestToViewTodoItemView() throws Exception {
                requestBuilder.create(formObject)
                        .andExpect(view().name(VIEW_NAME_VIEW_TODO_ITEM_VIEW))
                        .andExpect(model().attribute(
                                MODEL_ATTRIBUTE_NAME_ID, equalTo(ID.toString())));
            }

            @Test
            @DisplayName("Should display the correct flash message")
            void shouldDisplayCorrectFlashMessage() throws Exception {
                requestBuilder.create(formObject)
                        .andExpect(flash().attribute(
                                FLASH_ATTRIBUTE_KEY_FEEDBACK_MESSAGE,
                                equalTo(FEEDBACK_MESSAGE)
                        ));
            }

            @Test
            @DisplayName("Should create a new todo item with the correct description")
            void shouldCreateNewTodoItemWithCorrectDescription() throws Exception {
                requestBuilder.create(formObject);

                verify(service, times(1)).create(assertArg(
                        todoItem -> assertThat(todoItem.getDescription())
                                .isEqualTo(DESCRIPTION)
                ));
            }

            @Test
            @DisplayName("Should create a new todo item with the correct title")
            void shouldCreateNewTodoItemWithCorrectTitle() throws Exception {
                requestBuilder.create(formObject);

                verify(service, times(1)).create(assertArg(
                        todoItem -> assertThat(todoItem.getTitle())
                                .isEqualTo(TITLE)
                ));
            }
        }
    }
}

Wir können jetzt Unit-Tests für eine Controller-Methode schreiben, die ein Formular übermittelt. Fassen wir zusammen, was wir aus diesem Blogbeitrag gelernt haben.

Zusammenfassung

Dieser Blogbeitrag hat uns sechs Dinge beigebracht:

  • Wir können die Feldwerte des übermittelten Formulars konfigurieren, indem wir den param() verwenden Methode des MockHttpServletRequestBuilder Klasse.
  • Wenn wir sicherstellen müssen, dass das zu testende System X Validierungsfehler anzeigt, müssen wir den attributeErrorCount() aufrufen Methode des ModelResultMatchers Klasse.
  • Wenn wir überprüfen müssen, ob das zu testende System den korrekten Validierungsfehler anzeigt, müssen wir attributeHasFieldErrorCode() verwenden Methode des ModelResultMatchers Klasse.
  • Wenn wir sicherstellen müssen, dass Felder des gerenderten Formulars korrekte Informationen enthalten, müssen wir den attribute() aufrufen Methode des ModelResultMatchers Klasse.
  • Wenn wir überprüfen müssen, ob die HTTP-Anfrage auf den richtigen Pfad umgeleitet wird, müssen wir den name() verwenden Methode des ViewResultMatchers Klasse.
  • Wenn wir sicherstellen müssen, dass das zu testende System dem Benutzer die richtige Flash-Nachricht anzeigt, müssen wir den attribute() aufrufen Methode des FlashAttributeResultMatchers Klasse.

Java-Tag