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

Unit Tests für eine Spring MVC REST API schreiben:Daten schreiben

In den vorherigen Teilen meines Spring MVC Test-Tutorials wurde beschrieben, wie Sie Komponententests für eine Spring MVC-REST-API schreiben können, wenn das zu testende System die Informationen eines einzelnen Elements oder eine Liste zurückgibt. Mit anderen Worten, jetzt wissen wir, wie wir Komponententests für Spring MVC-Controller schreiben können, die Daten als JSON zurückgeben.

Es ist Zeit, den nächsten Schritt zu tun. Dieser Blogbeitrag beschreibt, wie wir Unit-Tests für einen Spring-MVC-REST-API-Endpunkt schreiben können, der Daten aus dem Anfragetext liest, gültige Daten in eine Datenbank einfügt und Daten als JSON zurückgibt.

Nachdem wir diesen Blogbeitrag fertiggestellt haben, werden wir:

  • Wissen Sie, wie wir POST senden können Anfragen an das zu testende System und konfigurieren Sie den Anfragetext der HTTP-Anfrage.
  • Verstehen, wie wir sicherstellen können, dass das zu testende System wie erwartet funktioniert, wenn die Validierung fehlschlägt.
  • Wissen, wie wir sicherstellen können, dass das zu testende System wie erwartet funktioniert, wenn die Validierung erfolgreich ist.

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-item'. Der Vertrag dieses API-Endpunkts wird im Folgenden beschrieben:

  • Die Validierungsregeln müssen mithilfe der Jakarta Bean Validation API angegeben werden.
  • Wenn die Validierung fehlschlägt, gibt das getestete System den HTTP-Statuscode 400 zurück.
  • Wenn die Validierung fehlschlägt, gibt das getestete System ein JSON-Dokument zurück, das die in den Eingabedaten gefundenen Validierungsfehler beschreibt.
  • Wenn ein neues Aufgabenelement erfolgreich erstellt wurde, gibt das zu testende System den HTTP-Statuscode 201 zurück.
  • Wenn ein neues Todo-Element erfolgreich erstellt wurde, gibt das zu testende System ein JSON-Dokument zurück, das die Informationen des erstellten Todo-Elements enthält.

Die folgenden Beispiele veranschaulichen die JSON-Dokumente, die an den Client zurückgesendet werden:

Beispiel 1:Der Client hat versucht, ein neues Aufgabenelement ohne Titel zu erstellen

{
    "fieldErrors":[
        {
            "field":"title",
            "errorCode":"NotBlank"
        }
    ]
}

Beispiel 2:Ein neues Aufgabenelement wurde erfolgreich erstellt

{
    "id":1,
    "description":"This is just an example",
    "tags":[],
    "title":"Create a new todo item",
    "status":"OPEN"
}

Die getestete Controller-Methode heißt create() . Es speichert einfach ein neues Todo-Element in der Datenbank und gibt die Informationen des erstellten Todo-Elements zurück. Der Quellcode der getesteten Controller-Methode sieht wie folgt aus:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
@RequestMapping("/todo-item")
public class TodoItemCrudController {
    private final TodoItemCrudService service;

    @Autowired
    public TodoItemCrudController(TodoItemCrudService service) {
        this.service = service;
    }
    
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public TodoItemDTO create(@RequestBody @Valid CreateTodoItemDTO input) {
        return service.create(input);
    }
}

Die CreateTodoItemDTO Klasse enthält die Informationen des erstellten ToDo-Elements. Außerdem werden die Validierungsregeln deklariert, die zur Validierung dieser Informationen verwendet werden. Sein Quellcode sieht wie folgt aus:

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

public class CreateTodoItemDTO {

    @Size(max = 1000)
    private String description;

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

    //Getters and setters are omitted
}

Die TodoItemDTO Klasse enthält die Informationen des erstellten ToDo-Elements. Sein Quellcode sieht wie folgt aus:

import java.util.List;

public class TodoItemDTO {

    private Long id;
    private String description;
    private List<TagDTO> tags;
    private String title;
    private TodoItemStatus status;

    //Getters and setters are omitted
}

Die TagDTO Klasse enthält die Informationen eines einzelnen Tags. Sein Quellcode sieht wie folgt aus:

public class TagDTO {

    private Long id;
    private String name;

    //Getters and setters are omitted
}

Die TodoItemStatus enum gibt die möglichen Status eines Aufgabeneintrags an. Sein Quellcode sieht wie folgt aus:

public enum TodoItemStatus {
    OPEN,
    IN_PROGRESS,
    DONE
}

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 Daten in der Datenbank speichert und Daten als JSON zurückgibt, müssen wir lernen, wie wir Zusicherungen für die vom zu testenden System zurückgegebene HTTP-Antwort schreiben können. Wenn wir Zusicherungen für die vom getesteten Spring MVC-Controller zurückgegebene HTTP-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.
  • Die content() Methode gibt einen ContentResultMatchers zurück -Objekt, das es uns ermöglicht, Zusicherungen für den Inhalt der zurückgegebenen HTTP-Antwort zu schreiben.
  • Die jsonPath() Methode gibt einen JsonPathResultMatchers zurück -Objekt, das es uns ermöglicht, Assertionen für den Text der zurückgegebenen HTTP-Antwort zu schreiben, indem wir JsonPath-Ausdrücke und Hamcrest-Matcher verwenden.

Da wir Behauptungen mithilfe von JsonPath-Ausdrücken und Hamcrest-Matchern schreiben, müssen wir sicherstellen, dass der json-path und hamcrest-library Abhängigkeiten werden aus dem Klassenpfad gefunden. Wenn wir die Maven- und Spring Boot-Abhängigkeitsverwaltung verwenden, können wir diese Abhängigkeiten deklarieren, indem wir das folgende XML-Snippet zum dependencies hinzufügen Abschnitt unserer POM-Datei:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-library</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <scope>test</scope>
</dependency>

Lassen Sie uns weitermachen und herausfinden, wie wir eine Request-Builder-Methode schreiben können, die POST sendet Anfragen an das zu testende System.

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 erstellt und an das zu testende System sendet. Wir können diese Request-Builder-Methode schreiben, indem wir diesen Schritten folgen:

  1. Fügen Sie einen private hinzu und static Methode namens convertObjectToJsonBytes() unsere Request-Builder-Klasse und stellen Sie sicher, dass diese Methode ein Byte-Array zurückgibt.
  2. Stellen Sie sicher, dass der convertObjectToJsonBytes() Methode nimmt einen Object object als Methodenparameter und konvertiert dieses Objekt in ein Byte-Array, das ein JSON-Dokument enthält.
  3. Fügen Sie eine neue Methode namens create() hinzu zu unserer Request-Builder-Klasse. Stellen Sie sicher, dass diese Methode einen CreateTodoItemDTO akzeptiert Objekt als Methodenparameter und gibt ein ResultActions zurück Objekt.
  4. Senden Sie einen POST Anfrage an den Pfad:'/todo-item' durch Aufrufen des perform() Methode des MockMvc Klasse. Denken Sie daran, die Informationen des erstellten Todo-Elements in ein JSON-Dokument zu konvertieren und diese Informationen zum Hauptteil der HTTP-Anforderung hinzuzufügen.
  5. Gib den ResultActions zurück Objekt, das von perform() zurückgegeben wird Methode.

Nachdem wir unsere Request-Builder-Methode geschrieben haben, sieht der Quellcode unserer Request-Builder-Klasse wie folgt aus:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

import java.io.IOException;

import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.objectMapper;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;

class TodoItemRequestBuilder {

    private final MockMvc mockMvc;

    TodoItemRequestBuilder(MockMvc mockMvc) {
        this.mockMvc = mockMvc;
    }
    
    ResultActions create(CreateTodoItemDTO input) throws Exception {
        return mockMvc.perform(post("/todo-item")
                .contentType(MediaType.APPLICATION_JSON)
                .content(convertObjectToJsonBytes(input))
        );
    }

    private static byte[] convertObjectToJsonBytes(Object object) throws IOException {
        ObjectMapper mapper = objectMapper();
        return mapper.writeValueAsBytes(object);
    }
}

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 Create 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 WhenInvalidInformationIsProvided hinzu zum Create 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 WhenFieldValuesAreEmptyStrings hinzu zum WhenInvalidInformationIsProvided Klasse. Diese innere Klasse enthält die Testmethoden, die sicherstellen, dass das zu testende System wie erwartet funktioniert, wenn der title und description des erstellten Aufgabeneintrags sind leere Zeichenfolgen.
  4. Fügen Sie eine innere Klasse namens WhenValidInformationIsProvided hinzu zum Create 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.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.*;
import static org.mockito.Mockito.mock;

class TodoItemCrudControllerTest {

    private TodoItemRequestBuilder requestBuilder;
    private TodoItemCrudService service;

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

        TodoItemCrudController testedController = new TodoItemCrudController(service);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(testedController)
                .setControllerAdvice(new TodoItemErrorHandler())
                .setMessageConverters(objectMapperHttpMessageConverter())
                .build();
        requestBuilder = new TodoItemRequestBuilder(mockMvc);
    }

    @Nested
    @DisplayName("Create a new todo item")
    class Create {

        @Nested
        @DisplayName("When the information of the created todo item isn't valid")
        class WhenInvalidInformationIsProvided {

            @Nested
            @DisplayName("When the field values are empty strings")
            class WhenFieldValuesAreEmptyStrings {

            }
        }

        @Nested
        @DisplayName("When the information of the created todo item is valid")
        class WhenValidInformationIsProvided {

        }
    }
}

Zweite , müssen wir einen private input hinzufügen Feld zum Create Klasse. Dieses Feld enthält einen Verweis auf den CreateTodoItemDTO Objekt, das die Informationen des erstellten Aufgabeneintrags enthält.

Nachdem wir dieses Feld zum Create hinzugefügt 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.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.*;
import static org.mockito.Mockito.mock;

class TodoItemCrudControllerTest {

    private TodoItemRequestBuilder requestBuilder;
    private TodoItemCrudService service;

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

        TodoItemCrudController testedController = new TodoItemCrudController(service);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(testedController)
                .setControllerAdvice(new TodoItemErrorHandler())
                .setMessageConverters(objectMapperHttpMessageConverter())
                .build();
        requestBuilder = new TodoItemRequestBuilder(mockMvc);
    }

    @Nested
    @DisplayName("Create a new todo item")
    class Create {

        private CreateTodoItemDTO input;

        @Nested
        @DisplayName("When the information of the created todo item isn't valid")
        class WhenInvalidInformationIsProvided {

            @Nested
            @DisplayName("When the field values are empty strings")
            class WhenFieldValuesAreEmptyStrings {

            }
        }

        @Nested
        @DisplayName("When the information of the created todo item is valid")
        class WhenValidInformationIsProvided {

        }
    }
}

Dritter müssen wir sicherstellen, dass das zu testende System wie erwartet funktioniert, wenn wir versuchen, ein neues Todo-Element zu erstellen, das einen leeren title hat und description . Wir können die erforderlichen Testmethoden schreiben, indem wir diesen Schritten folgen:

  1. Fügen Sie die erforderlichen Konstanten zu WhenFieldValuesAreEmptyStrings hinzu Klasse.
  2. Fügen Sie dem WhenFieldValuesAreEmptyStrings 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 einen neuen CreateTodoItemDTO erstellen Objekt, das einen leeren title hat und description , und speichern Sie das erstellte Objekt im input Feld.
  3. Stellen Sie sicher, dass das zu testende System den HTTP-Statuscode 400 zurückgibt.
  4. Vergewissern Sie sich, dass das zu testende System Validierungsfehler als JSON zurückgibt.
  5. Stellen Sie sicher, dass das zu testende System einen Validierungsfehler zurückgibt.
  6. Vergewissern Sie sich, dass das zu testende System einen Validierungsfehler zu einem leeren Titel zurückgibt.
  7. 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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.*;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
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.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class TodoItemCrudControllerTest {

    private TodoItemRequestBuilder requestBuilder;
    private TodoItemCrudService service;

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

        TodoItemCrudController testedController = new TodoItemCrudController(service);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(testedController)
                .setControllerAdvice(new TodoItemErrorHandler())
                .setMessageConverters(objectMapperHttpMessageConverter())
                .build();
        requestBuilder = new TodoItemRequestBuilder(mockMvc);
    }

    @Nested
    @DisplayName("Create a new todo item")
    class Create {

        private CreateTodoItemDTO input;

        @Nested
        @DisplayName("When the information of the created todo item isn't valid")
        class WhenInvalidInformationIsProvided {

            @Nested
            @DisplayName("When the field values are empty strings")
            class WhenFieldValuesAreEmptyStrings {

                private static final String VALIDATION_ERROR_EMPTY_VALUE = "NotBlank";

                @BeforeEach
                void createInputWithEmptyFieldValues() {
                    input = new CreateTodoItemDTO();
                    input.setDescription("");
                    input.setTitle("");
                }

                @Test
                @DisplayName("Should return the HTTP status code bad request (400)")
                void shouldReturnHttpStatusCodeBadRequest() throws Exception {
                    requestBuilder.create(input)
                            .andExpect(status().isBadRequest());
                }

                @Test
                @DisplayName("Should return validation errors as JSON")
                void shouldReturnValidationErrorsAsJson() throws Exception {
                    requestBuilder.create(input)
                            .andExpect(
                                    content().contentType(MediaType.APPLICATION_JSON)
                            );
                }

                @Test
                @DisplayName("Should return one validation error")
                void shouldReturnOneValidationError() throws Exception {
                    requestBuilder.create(input)
                            .andExpect(jsonPath("$.fieldErrors", hasSize(1)));
                }

                @Test
                @DisplayName("Should return a validation error about empty title")
                void shouldReturnValidationErrorAboutEmptyTitle() throws Exception {
                    requestBuilder.create(input)
                            .andExpect(jsonPath(
                                    "$.fieldErrors[?(@.field == 'title')].errorCode",
                                    contains(VALIDATION_ERROR_EMPTY_VALUE)
                            ));
                }

                @Test
                @DisplayName("Shouldn't create a new todo item")
                void shouldNotCreateNewTodoItem() throws Exception {
                    requestBuilder.create(input);

                    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 WhenValidInformationIsProvided hinzu Klasse.
  2. Fügen Sie dem WhenValidInformationIsProvided 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 einen neuen CreateTodoItemDTO Objekt mit gültigem title und description . Nachdem wir dieses Objekt erstellt haben, müssen wir es im input speichern Feld.
    • Stellen Sie sicher, dass der create() Methode des TodoItemCrudService Die Klasse gibt die Informationen des erstellten ToDo-Elements zurück.
  3. Stellen Sie sicher, dass das zu testende System den HTTP-Statuscode 201 zurückgibt.
  4. Vergewissern Sie sich, dass das zu testende System die Informationen des erstellten Todo-Elements als JSON zurückgibt.
  5. Stellen Sie sicher, dass das zu testende System die Informationen des erstellten Todo-Elements zurückgibt.
  6. Vergewissern Sie sich, dass das zu testende System ein neues Aufgabenelement mit der richtigen Beschreibung erstellt.
  7. Stellen Sie sicher, 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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import java.util.ArrayList;

import static info.solidsoft.mockito.java8.AssertionMatcher.assertArg;
import static net.petrikainulainen.springmvctest.junit5.web.WebTestConfig.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
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.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class TodoItemCrudControllerTest {

    private TodoItemRequestBuilder requestBuilder;
    private TodoItemCrudService service;

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

        TodoItemCrudController testedController = new TodoItemCrudController(service);
        MockMvc mockMvc = MockMvcBuilders.standaloneSetup(testedController)
                .setControllerAdvice(new TodoItemErrorHandler())
                .setMessageConverters(objectMapperHttpMessageConverter())
                .build();
        requestBuilder = new TodoItemRequestBuilder(mockMvc);
    }

    @Nested
    @DisplayName("Create a new todo item")
    class Create {

        private CreateTodoItemDTO input;

        //The other inner class is omitted

        @Nested
        @DisplayName("When the information of the created todo item is valid")
        class WhenValidInformationIsProvided {

            private static final int MAX_LENGTH_DESCRIPTION = 1000;
            private static final int MAX_LENGTH_TITLE = 100;

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

            @BeforeEach
            void configureSystemUnderTest() {
                input = createInputWithValidInformation();
                returnCreatedTodoItem();
            }

            private CreateTodoItemDTO createInputWithValidInformation() {
                CreateTodoItemDTO input = new CreateTodoItemDTO();
                input.setDescription(DESCRIPTION);
                input.setTitle(TITLE);
                return input;
            }

            private void returnCreatedTodoItem() {
                TodoItemDTO created = new TodoItemDTO();
                created.setId(ID);
                created.setDescription(DESCRIPTION);
                created.setStatus(TodoItemStatus.OPEN);
                created.setTags(new ArrayList<>());
                created.setTitle(TITLE);

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

            @Test
            @DisplayName("Should return the HTTP status status code created (201)")
            void shouldReturnHttpStatusCodeCreated() throws Exception {
                requestBuilder.create(input)
                        .andExpect(status().isCreated());
            }

            @Test
            @DisplayName("Should return the information of the created todo item as JSON")
            void shouldReturnInformationOfCreatedTodoItemAsJSON() throws Exception {
                requestBuilder.create(input)
                        .andExpect(content().contentType(MediaType.APPLICATION_JSON));
            }

            @Test
            @DisplayName("Should return the information of the created todo item")
            void shouldReturnInformationOfCreatedTodoItem() throws Exception {
                requestBuilder.create(input)
                        .andExpect(jsonPath("$.id", equalTo(ID.intValue())))
                        .andExpect(jsonPath("$.description", equalTo(DESCRIPTION)))
                        .andExpect(jsonPath("$.status", 
                                equalTo(TodoItemStatus.OPEN.name())
                        ))
                        .andExpect(jsonPath("$.tags", hasSize(0)))
                        .andExpect(jsonPath("$.title", equalTo(TITLE)));
            }

            @Test
            @DisplayName("Should create a new todo item with the correct description")
            void shouldCreateNewTodoItemWithCorrectDescription() throws Exception {
                requestBuilder.create(input);
                verify(service, times(1)).create(assertArg(
                        created -> assertThat(created.getDescription())
                                .isEqualTo(DESCRIPTION)
                ));
            }

            @Test
            @DisplayName("Should create a new todo item with the correct title")
            void shouldCreateNewTodoItemWithCorrectTitle() throws Exception {
                requestBuilder.create(input);
                verify(service, times(1)).create(assertArg(
                        created -> assertThat(created.getTitle())
                                .isEqualTo(TITLE)
                ));
            }
        }
    }
}

Wir können jetzt Komponententests für einen Spring MVC REST API-Endpunkt schreiben, der Daten in die Datenbank einfügt und Daten als JSON zurückgibt. Fassen wir zusammen, was wir aus diesem Blogbeitrag gelernt haben.

Zusammenfassung

Dieser Blogbeitrag hat uns vier Dinge gelehrt:

  • Wenn wir Zusicherungen für den zurückgegebenen HTTP-Status schreiben wollen, müssen wir den status() aufrufen Methode des MockMvcResultMatchers Klasse.
  • Wenn wir Zusicherungen für den Inhalt der zurückgegebenen HTTP-Antwort schreiben wollen, müssen wir den content() aufrufen Methode des MockMvcResultMatchers Klasse.
  • Wenn wir Zusicherungen für den Hauptteil der zurückgegebenen HTTP-Antwort schreiben möchten, indem wir JsonPath-Ausdrücke und Hamcrest-Matcher verwenden, müssen wir den jsonPath() aufrufen Methode des MockMvcResultMatchers Klasse.
  • Wenn wir Zusicherungen für den Hauptteil der zurückgegebenen HTTP-Antwort schreiben möchten, indem wir JsonPath-Ausdrücke und Hamcrest-Matcher verwenden, müssen wir sicherstellen, dass der json-path und hamcrest-library Abhängigkeiten werden aus dem Klassenpfad gefunden

Java-Tag