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

Komponententests von Spring MVC-Controllern:REST-API

Dieser Blogbeitrag ist veraltet! Wenn Sie erfahren möchten, wie Sie Unit-Tests für eine Spring MVC REST API schreiben können, sollten Sie sich mein aktualisiertes Spring MVC Test Tutorial ansehen. Es beschreibt, wie Sie Unit-Tests für eine Spring MVC REST API mit JUnit 5 schreiben können.

Spring MVC bietet eine einfache Möglichkeit zum Erstellen von REST-APIs. Das Schreiben umfassender und schneller Komponententests für diese APIs war jedoch mühsam. Die Veröffentlichung des Spring MVC Test Frameworks gab uns die Möglichkeit, Komponententests zu schreiben, die lesbar, umfassend und schnell sind.

Dieser Blogbeitrag beschreibt, wie wir Komponententests für eine REST-API schreiben können, indem wir das Spring MVC Test-Framework verwenden. In diesem Blogbeitrag werden wir Unit-Tests für Controller-Methoden schreiben, die CRUD-Funktionen für Aufgabeneinträge bereitstellen.

Fangen wir an.

Erforderliche Abhängigkeiten mit Maven erhalten

Wir können die erforderlichen Testabhängigkeiten erhalten, indem wir unserer POM-Datei die folgenden Abhängigkeitsdeklarationen hinzufügen:

  • Hamcrest 1.3 (hamcrest-all ). Wir verwenden Hamcrest-Matcher, wenn wir Zusicherungen für die Antworten schreiben.
  • Juni 4.11. Wir müssen den hamcrest-core ausschließen Abhängigkeit, weil wir bereits hamcrest-all hinzugefügt haben Abhängigkeit.
  • Mockito 1.9.5 (mockito-core ). Wir verwenden Mockito als unsere spöttische Bibliothek.
  • Frühjahrstest 3.2.3.FREIGABE
  • JsonPath 0.8.1 (json-Pfad und json-path-assert ). Wir verwenden JsonPath, wenn wir Zusicherungen für JSON-Dokumente schreiben, die von unserer REST-API zurückgegeben werden.

Die entsprechenden Abhängigkeitserklärungen sehen wie folgt aus:

<dependency>
	<groupId>org.hamcrest</groupId>
	<artifactId>hamcrest-all</artifactId>
	<version>1.3</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.11</version>
	<scope>test</scope>
	<exclusions>
		<exclusion>
			<artifactId>hamcrest-core</artifactId>
			<groupId>org.hamcrest</groupId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.mockito</groupId>
	<artifactId>mockito-core</artifactId>
	<version>1.9.5</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>3.2.3.RELEASE</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>com.jayway.jsonpath</groupId>
	<artifactId>json-path</artifactId>
	<version>0.8.1</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>com.jayway.jsonpath</groupId>
	<artifactId>json-path-assert</artifactId>
	<version>0.8.1</version>
	<scope>test</scope>
</dependency>

Lassen Sie uns weitermachen und ein wenig über die Konfiguration unserer Unit-Tests sprechen.

Konfigurieren unserer Komponententests

Die Komponententests, die wir in diesem Blogbeitrag schreiben werden, verwenden die kontextbasierte Konfiguration der Webanwendung. Das bedeutet, dass wir die Spring MVC-Infrastruktur konfigurieren, indem wir entweder eine Anwendungskontext-Konfigurationsklasse oder eine XML-Konfigurationsdatei verwenden.

Da im ersten Teil dieses Tutorials die Prinzipien beschrieben wurden, die wir befolgen sollten, wenn wir den Anwendungskontext unserer Anwendung konfigurieren, wird dieses Problem in diesem Blogbeitrag nicht behandelt.

Allerdings gibt es eine Sache, die wir hier ansprechen müssen.

Die Konfigurationsklasse (oder -datei) des Anwendungskontexts, die die Webschicht unserer Beispielanwendung konfiguriert, erstellt keine Exception-Resolver-Bean. Der SimpleMappingExceptionResolver Klasse, die in früheren Teilen dieses Tutorials verwendet wurde, ordnet den Namen der Ausnahmeklasse der Ansicht zu, die gerendert wird, wenn die konfigurierte Ausnahme ausgelöst wird.

Dies ist sinnvoll, wenn wir eine "normale" Spring MVC-Anwendung implementieren. Wenn wir jedoch eine REST-API implementieren, möchten wir Ausnahmen in HTTP-Statuscodes umwandeln. Dieses Verhalten wird vom ResponseStatusExceptionResolver bereitgestellt Klasse, die standardmäßig aktiviert ist.

Unsere Beispielanwendung verfügt auch über eine benutzerdefinierte Ausnahmebehandlungsklasse, die mit der Annotation @ControllerAdvice versehen ist. Diese Klasse behandelt Validierungsfehler und anwendungsspezifische Ausnahmen. Wir werden später in diesem Blogbeitrag mehr über diese Klasse sprechen.

Lassen Sie uns weitermachen und herausfinden, wie wir Unit-Tests für unsere REST-API schreiben können.

Einheitentests für eine REST-API schreiben

Bevor wir mit dem Schreiben von Komponententests für unsere REST-API beginnen können, müssen wir zwei Dinge verstehen:

  • Wir müssen wissen, was die Kernkomponenten des Spring MVC Test Frameworks sind. Diese Komponenten werden im zweiten Teil dieses Tutorials beschrieben.
  • Wir müssen wissen, wie wir Assertionen für JSON-Dokumente schreiben können, indem wir JsonPath-Ausdrücke verwenden. Wir können diese Informationen erhalten, indem wir meinen Blogbeitrag lesen, der beschreibt, wie wir mit JsonPath saubere Behauptungen schreiben können.

Als Nächstes sehen wir uns das Spring MVC Test Framework in Aktion an und schreiben Komponententests für die folgenden Controller-Methoden:

  • Die erste Controller-Methode gibt eine Liste von Aufgabeneinträgen zurück.
  • Die zweite Controller-Methode gibt die Informationen eines einzelnen Aufgabeneintrags zurück.
  • Die dritte Controller-Methode fügt der Datenbank einen neuen Aufgabeneintrag hinzu und gibt den hinzugefügten Aufgabeneintrag zurück.

Todo-Einträge abrufen

Die erste Controller-Methode gibt eine Liste von Todo-Einträgen zurück, die aus der Datenbank gefunden werden. Sehen wir uns zunächst die Implementierung dieser Methode an.

Erwartetes Verhalten

Die Controller-Methode, die alle in der Datenbank gespeicherten Todo-Einträge zurückgibt, wird folgendermaßen implementiert:

  1. Es verarbeitet GET Anfragen werden an die URL '/api/todo' gesendet.
  2. Erhält eine Liste von Todo Objekte, indem Sie findAll() aufrufen Methode des TodoService Schnittstelle. Diese Methode gibt alle Aufgabeneinträge zurück, die in der Datenbank gespeichert sind. Diese Aufgabeneinträge werden immer in der gleichen Reihenfolge zurückgegeben.
  3. Es wandelt die empfangene Liste in eine Liste von TodoDTO um Objekte.
  4. Es gibt die Liste zurück, die TodoDTO enthält Objekte.

Der relevante Teil des TodoController Klasse sieht wie folgt aus:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@Controller
public class TodoController {

    private TodoService service;

    @RequestMapping(value = "/api/todo", method = RequestMethod.GET)
    @ResponseBody
    public List<TodoDTO> findAll() {
        List<Todo> models = service.findAll();
        return createDTOs(models);
    }

    private List<TodoDTO> createDTOs(List<Todo> models) {
        List<TodoDTO> dtos = new ArrayList<>();

        for (Todo model: models) {
            dtos.add(createDTO(model));
        }

        return dtos;
    }

    private TodoDTO createDTO(Todo model) {
        TodoDTO dto = new TodoDTO();

        dto.setId(model.getId());
        dto.setDescription(model.getDescription());
        dto.setTitle(model.getTitle());

        return dto;
    }
}

Wenn eine Liste von TodoDTO objects zurückgegeben wird, wandelt Spring MVC diese Liste in ein JSON-Dokument um, das eine Sammlung von Objekten enthält. Das zurückgegebene JSON-Dokument sieht wie folgt aus:

[
    {
        "id":1,
        "description":"Lorem ipsum",
        "title":"Foo"
    },
    {
        "id":2,
        "description":"Lorem ipsum",
        "title":"Bar"
    }
]

Fahren wir fort und schreiben einen Komponententest, der sicherstellt, dass diese Controller-Methode wie erwartet funktioniert.

Test:Aufgabeneinträge gefunden

Wir können einen Unit-Test für diese Controller-Methode schreiben, indem wir diesen Schritten folgen:

  1. Erstellen Sie die Testdaten, die zurückgegeben werden, wenn findAll() Methode des TodoService Schnittstelle aufgerufen wird. Wir erstellen die Testdaten mithilfe einer Testdaten-Builder-Klasse.
  2. Konfigurieren Sie unser Scheinobjekt so, dass es die erstellten Testdaten zurückgibt, wenn es findAll() ist Methode aufgerufen wird.
  3. Führen Sie ein GET aus Anfrage an URL '/api/todo'.
  4. Vergewissern Sie sich, dass der HTTP-Statuscode 200 zurückgegeben wird.
  5. Stellen Sie sicher, dass der Inhaltstyp der Antwort „application/json“ und der Zeichensatz „UTF-8“ ist.
  6. Rufen Sie die Sammlung von Aufgabeneinträgen mit dem JsonPath-Ausdruck $ ab und stellen Sie sicher, dass zwei Aufgabeneinträge zurückgegeben werden.
  7. Holen Sie sich die ID , Beschreibung , und Titel des ersten Aufgabeneintrags mithilfe von JsonPath-Ausdrücken $[0].id , $[0].Beschreibung , und $[0].title . Überprüfen Sie, ob die richtigen Werte zurückgegeben werden.
  8. Holen Sie sich die ID , Beschreibung , und Titel des zweiten Aufgabeneintrags mithilfe von JsonPath-Ausdrücken $[1].id , $[1].Beschreibung , und $[1].title . Überprüfen Sie, ob die richtigen Werte zurückgegeben werden.
  9. Vergewissern Sie sich, dass findAll() Methode des TodoService Schnittstelle wird nur einmal aufgerufen.
  10. Stellen Sie sicher, dass während des Tests keine anderen Methoden unseres Mock-Objekts aufgerufen werden.

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Arrays;

import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here.

    //The setUp() method is omitted.

    @Test
    public void findAll_TodosFound_ShouldReturnFoundTodoEntries() throws Exception {
        Todo first = new TodoBuilder()
                .id(1L)
                .description("Lorem ipsum")
                .title("Foo")
                .build();
        Todo second = new TodoBuilder()
                .id(2L)
                .description("Lorem ipsum")
                .title("Bar")
                .build();

        when(todoServiceMock.findAll()).thenReturn(Arrays.asList(first, second));

        mockMvc.perform(get("/api/todo"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].id", is(1)))
                .andExpect(jsonPath("$[0].description", is("Lorem ipsum")))
                .andExpect(jsonPath("$[0].title", is("Foo")))
                .andExpect(jsonPath("$[1].id", is(2)))
                .andExpect(jsonPath("$[1].description", is("Lorem ipsum")))
                .andExpect(jsonPath("$[1].title", is("Bar")));

        verify(todoServiceMock, times(1)).findAll();
        verifyNoMoreInteractions(todoServiceMock);
    }
}

Unser Komponententest verwendet eine Konstante namens APPLICATION_JSON_UTF8 die im TestUtil deklariert ist Klasse. Der Wert dieser Konstante ist ein MediaType Objekt, dessen Inhaltstyp „application/json“ und der Zeichensatz „UTF-8“ ist.

Der relevante Teil des TestUtil Klasse sieht wie folgt aus:

public class TestUtil {

    public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(),
																		MediaType.APPLICATION_JSON.getSubtype(), 						
																		Charset.forName("utf8")						
																		);
}

Todo-Eintrag erhalten

Die zweite Controller-Methode, die wir testen müssen, gibt die Informationen eines einzelnen Todo-Eintrags zurück. Lassen Sie uns herausfinden, wie diese Controller-Methode implementiert wird.

Erwartetes Verhalten

Die Controller-Methode, die die Informationen eines einzelnen Aufgabeneintrags zurückgibt, wird folgendermaßen implementiert:

  1. Es verarbeitet GET Anfragen werden an die URL „/api/todo/{id}“ gesendet. Die {id} ist eine Pfadvariable, die die id enthält des angeforderten Aufgabeneintrags.
  2. Es erhält den angeforderten Todo-Eintrag, indem es findById() aufruft Methode des TodoService Schnittstelle und übergibt die id des angeforderten Todo-Eintrags als Methodenparameter. Diese Methode gibt den gefundenen Aufgabeneintrag zurück. Wenn kein Aufgabeneintrag gefunden wird, löst diese Methode eine TodoNotFoundException aus .
  3. Es transformiert das Todo Objekt in ein TodoDTO Objekt.
  4. Es gibt das erstellte TodoDTO zurück Objekt.

Der Quellcode unserer Controller-Methode sieht wie folgt aus:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class TodoController {

    private TodoService service;

    @RequestMapping(value = "/api/todo/{id}", method = RequestMethod.GET)
    @ResponseBody
    public TodoDTO findById(@PathVariable("id") Long id) throws TodoNotFoundException {
        Todo found = service.findById(id);
        return createDTO(found);
    }

    private TodoDTO createDTO(Todo model) {
        TodoDTO dto = new TodoDTO();

        dto.setId(model.getId());
        dto.setDescription(model.getDescription());
        dto.setTitle(model.getTitle());

        return dto;
    }
}

Das JSON-Dokument, das an den Client zurückgegeben wird, sieht folgendermaßen aus:

{
    "id":1,
    "description":"Lorem ipsum",
    "title":"Foo"
}

Unsere nächste Frage lautet:

Was passiert, wenn eine TodoNotFoundException ausgelöst wird?

Unsere Beispielanwendung verfügt über eine Ausnahmebehandlungsklasse, die anwendungsspezifische Ausnahmen behandelt, die von unseren Controller-Klassen ausgelöst werden. Diese Klasse hat eine Ausnahmebehandlungsmethode, die aufgerufen wird, wenn eine TodoNotFoundException auftritt ist geworfen. Die Implementierung dieser Methode schreibt eine neue Protokollnachricht in die Protokolldatei und stellt sicher, dass der HTTP-Statuscode 404 an den Client zurückgesendet wird.

Der relevante Teil des RestErrorHandler Klasse sieht wie folgt aus:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class RestErrorHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestErrorHandler.class);

    @ExceptionHandler(TodoNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public void handleTodoNotFoundException(TodoNotFoundException ex) {
        LOGGER.debug("handling 404 error on a todo entry");
    }
}

Für diese Controller-Methode müssen wir zwei Unit-Tests schreiben:

  1. Wir müssen einen Test schreiben, der sicherstellt, dass unsere Anwendung ordnungsgemäß funktioniert, wenn der Todo-Eintrag nicht gefunden wird.
  2. Wir müssen einen Test schreiben, der überprüft, ob die richtigen Daten an den Client zurückgegeben werden, wenn der Todo-Eintrag gefunden wird.

Mal sehen, wie wir diese Tests schreiben können.

Test 1:Aufgabeneintrag wurde nicht gefunden

Zunächst müssen wir sicherstellen, dass unsere Anwendung ordnungsgemäß funktioniert, wenn kein Todo-Eintrag gefunden wird. Wir können einen Komponententest schreiben, der dies sicherstellt, indem wir diesen Schritten folgen:

  1. Konfigurieren Sie unser Scheinobjekt so, dass es eine TodoNotFoundException auslöst wenn es findById() ist Methode aufgerufen wird und die id des angeforderten ToDo-Eintrags beträgt 1L.
  2. Führen Sie ein GET aus Anfrage an URL '/api/todo/1'.
  3. Vergewissern Sie sich, dass der HTTP-Statuscode 404 zurückgegeben wird.
  4. Stellen Sie sicher, dass die findById() Methode des TodoService Die Schnittstelle wird nur einmal mit dem richtigen Methodenparameter (1L) aufgerufen.
  5. Stellen Sie sicher, dass keine anderen Methoden des TodoService Schnittstelle werden während dieses Tests aufgerufen.

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here.

    //The setUp() method is omitted.

    @Test
    public void findById_TodoEntryNotFound_ShouldReturnHttpStatusCode404() throws Exception {
        when(todoServiceMock.findById(1L)).thenThrow(new TodoNotFoundException(""));

        mockMvc.perform(get("/api/todo/{id}", 1L))
                .andExpect(status().isNotFound());

        verify(todoServiceMock, times(1)).findById(1L);
        verifyNoMoreInteractions(todoServiceMock);
    }
}

Test 2:Aufgabeneintrag gefunden

Zweitens müssen wir einen Test schreiben, der sicherstellt, dass die richtigen Daten zurückgegeben werden, wenn der angeforderte Todo-Eintrag gefunden wird. Wir können einen Test schreiben, der dies sicherstellt, indem wir diesen Schritten folgen:

  1. Erstellen Sie die Todo Objekt, das zurückgegeben wird, wenn unsere Service-Methode aufgerufen wird. Wir erstellen dieses Objekt mit unserem Test Data Builder.
  2. Konfigurieren Sie unser Mock-Objekt, um die erstellte Todo zurückzugeben Objekt, wenn es findById() ist Die Methode wird mit einem Methodenparameter 1L.
  3. aufgerufen
  4. Führen Sie ein GET aus Anfrage an URL '/api/todo/1'.
  5. Vergewissern Sie sich, dass der HTTP-Statuscode 200 zurückgegeben wird.
  6. Stellen Sie sicher, dass der Inhaltstyp der Antwort „application/json“ und der Zeichensatz „UTF-8“ ist.
  7. Holen Sie sich die ID des todo-Eintrags mithilfe des JsonPath-Ausdrucks $.id und vergewissern Sie sich, dass die id ist 1.
  8. Holen Sie sich die Beschreibung des todo-Eintrags mithilfe des JsonPath-Ausdrucks $.description und vergewissern Sie sich, dass die Beschreibung ist "Lorem ipsum".
  9. Hol dir den Titel des todo-Eintrags mithilfe des JsonPath-Ausdrucks $.title und vergewissern Sie sich, dass der Titel "Foo" lautet.
  10. Stellen Sie sicher, dass die findById() Methode des TodoService Die Schnittstelle wird nur einmal mit dem richtigen Methodenparameter (1L) aufgerufen.
  11. Stellen Sie sicher, dass die anderen Methoden unseres Mock-Objekts während des Tests nicht aufgerufen werden.

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here.

    //The setUp() method is omitted.

    @Test
    public void findById_TodoEntryFound_ShouldReturnFoundTodoEntry() throws Exception {
        Todo found = new TodoBuilder()
                .id(1L)
                .description("Lorem ipsum")
                .title("Foo")
                .build();

        when(todoServiceMock.findById(1L)).thenReturn(found);

        mockMvc.perform(get("/api/todo/{id}", 1L))
                .andExpect(status().isOk())
                .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.description", is("Lorem ipsum")))
                .andExpect(jsonPath("$.title", is("Foo")));

        verify(todoServiceMock, times(1)).findById(1L);
        verifyNoMoreInteractions(todoServiceMock);
    }
}

Neuen Aufgabeneintrag hinzufügen

Das dritte Controller-Verfahren fügt der Datenbank einen neuen Todo-Eintrag hinzu und gibt die Informationen des hinzugefügten Todo-Eintrags zurück. Lassen Sie uns weitermachen und herausfinden, wie es implementiert wird.

Erwartetes Verhalten

Die Controller-Methode, die der Datenbank neue Aufgabeneinträge hinzufügt, wird folgendermaßen implementiert:

  1. Es verarbeitet POST Anfragen werden an die URL '/api/todo' gesendet.
  2. Es validiert das TodoDTO Objekt als Methodenparameter angegeben. Wenn die Validierung fehlschlägt, eine MethodArgumentNotValidException wird geworfen.
  3. Es fügt der Datenbank einen neuen Aufgabeneintrag hinzu, indem es add() aufruft Methode des TodoService -Schnittstelle und übergibt das TodoDTO Objekt als Methodenparameter. Diese Methode fügt der Datenbank einen neuen Aufgabeneintrag hinzu und gibt den hinzugefügten Aufgabeneintrag zurück.
  4. Es transformiert das erstellte Todo Objekt in ein TodoDTO Objekt.
  5. Es gibt das TodoDTO zurück Objekt.

Der Quellcode unserer Controller-Methode sieht wie folgt aus:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Controller
public class TodoController {

    private TodoService service;

    @RequestMapping(value = "/api/todo", method = RequestMethod.POST)
    @ResponseBody
    public TodoDTO add(@Valid @RequestBody TodoDTO dto) {
        Todo added = service.add(dto);
        return createDTO(added);
    }

    private TodoDTO createDTO(Todo model) {
        TodoDTO dto = new TodoDTO();

        dto.setId(model.getId());
        dto.setDescription(model.getDescription());
        dto.setTitle(model.getTitle());

        return dto;
    }
}

Das TodoDTO class ist eine einfache DTO-Klasse, deren Quellcode wie folgt aussieht:

import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;

public class TodoDTO {

    private Long id;

    @Length(max = 500)
    private String description;

    @NotEmpty
    @Length(max = 100)
    private String title;

    //Constructor and other methods are omitted.
}

Wie wir sehen können, deklariert diese Klasse drei Validierungsbeschränkungen, die im Folgenden beschrieben werden:

  1. Die maximale Länge der Beschreibung beträgt 500 Zeichen.
  2. Der Titel eines Aufgabeneintrags darf nicht leer sein.
  3. Die maximale Länge des Titels ist 100 Zeichen lang.

Wenn die Validierung fehlschlägt, stellt unsere Fehlerbehandlungskomponente sicher, dass

  1. Der HTTP-Statuscode 400 wird an den Client zurückgegeben.
  2. Die Validierungsfehler werden als JSON-Dokument an den Client zurückgegeben.

Da ich bereits einen Blogbeitrag geschrieben habe, der beschreibt, wie wir eine Validierung zu einer REST-API hinzufügen können, wird die Implementierung der Fehlerbehandlungskomponente in diesem Blogbeitrag nicht behandelt.

Wir müssen jedoch wissen, welche Art von JSON-Dokument an den Client zurückgegeben wird, wenn die Validierung fehlschlägt. Diese Informationen finden Sie im Folgenden.

Wenn der Titel und die Beschreibung des TodoDTO -Objekt zu lang sind, wird das folgende JSON-Dokument an den Client zurückgegeben:

{
    "fieldErrors":[
        {
            "path":"description",
            "message":"The maximum length of the description is 500 characters."
        },
        {
            "path":"title",
            "message":"The maximum length of the title is 100 characters."
        }
    ]
}

Hinweis :Spring MVC garantiert nicht die Reihenfolge der Feldfehler. Mit anderen Worten, die Feldfehler werden in zufälliger Reihenfolge zurückgegeben. Wir müssen dies berücksichtigen, wenn wir Unit-Tests für diese Controller-Methode schreiben.

Wenn die Validierung andererseits nicht fehlschlägt, gibt unsere Controller-Methode das folgende JSON-Dokument an den Client zurück:

{
    "id":1,
    "description":"description",
    "title":"todo"
}

Für diese Controller-Methode müssen wir zwei Unit-Tests schreiben:

  1. Wir müssen einen Test schreiben, der sicherstellt, dass unsere Anwendung ordnungsgemäß funktioniert, wenn die Validierung fehlschlägt.
  2. Wir müssen einen Test schreiben, der sicherstellt, dass unsere Anwendung ordnungsgemäß funktioniert, wenn ein neuer Aufgabeneintrag zur Datenbank hinzugefügt wird.

Lassen Sie uns herausfinden, wie wir diese Tests schreiben können.

Test 1:Validierung schlägt fehl

Unser erster Test stellt sicher, dass unsere Anwendung ordnungsgemäß funktioniert, wenn die Validierung des hinzugefügten Aufgabeneintrags fehlschlägt. Wir können diesen Test schreiben, indem wir diesen Schritten folgen:

  1. Erstellen Sie einen Titel die 101 Zeichen hat.
  2. Erstellen Sie eine Beschreibung die 501 Zeichen hat.
  3. Erstellen Sie ein neues TodoDTO Objekt mit unserem Test Data Builder. Legen Sie den Titel fest und die Beschreibung des Objekts.
  4. Führen Sie einen POST aus Anfrage an die URL „/api/todo“. Legen Sie den Inhaltstyp der Anfrage auf „application/json“ fest. Stellen Sie den Zeichensatz der Anfrage auf „UTF-8“ ein. Transformieren Sie das erstellte TodoDTO Objekt in JSON-Bytes und senden Sie es im Hauptteil der Anfrage.
  5. Vergewissern Sie sich, dass der HTTP-Statuscode 400 zurückgegeben wird.
  6. Stellen Sie sicher, dass der Inhaltstyp der Antwort „application/json“ und ihr Inhaltstyp „UTF-8“ ist.
  7. Rufen Sie die Feldfehler mit dem JsonPath-Ausdruck $.fieldErrors ab und stellen Sie sicher, dass zwei Feldfehler zurückgegeben werden.
  8. Alle verfügbaren Pfade mit dem JsonPath-Ausdruck $.fieldErrors[*].path abrufen und stellen Sie sicher, dass Feldfehler zum Titel auftreten und Beschreibung Felder gefunden werden.
  9. Alle verfügbaren Fehlermeldungen abrufen, indem der JsonPath-Ausdruck $.fieldErrors[*].message verwendet wird und stellen Sie sicher, dass Fehlermeldungen zum Titel und Beschreibung Felder gefunden werden.
  10. Stellen Sie sicher, dass die Methoden unseres Mock-Objekts während unseres Tests nicht aufgerufen werden.

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here.

    //The setUp() method is omitted.

    @Test
    public void add_TitleAndDescriptionAreTooLong_ShouldReturnValidationErrorsForTitleAndDescription() throws Exception {
        String title = TestUtil.createStringWithLength(101);
        String description = TestUtil.createStringWithLength(501);

        TodoDTO dto = new TodoDTOBuilder()
                .description(description)
                .title(title)
                .build();

        mockMvc.perform(post("/api/todo")
                .contentType(TestUtil.APPLICATION_JSON_UTF8)
                .content(TestUtil.convertObjectToJsonBytes(dto))
        )
                .andExpect(status().isBadRequest())
                .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$.fieldErrors", hasSize(2)))
                .andExpect(jsonPath("$.fieldErrors[*].path", containsInAnyOrder("title", "description")))
                .andExpect(jsonPath("$.fieldErrors[*].message", containsInAnyOrder(
                        "The maximum length of the description is 500 characters.",
                        "The maximum length of the title is 100 characters."
                )));

        verifyZeroInteractions(todoServiceMock);
    }
}

Unser Komponententest verwendet zwei statische Methoden des TestUtil Klasse. Diese Methoden werden im Folgenden beschrieben:

  • Der createStringWithLength(int length) Methode erstellt einen neuen String Objekt mit der angegebenen Länge und gibt das erstellte Objekt zurück.
  • Das convertObjectToJsonBytes(Objektobjekt) -Methode konvertiert das als Methodenparameter angegebene Objekt in ein JSON-Dokument und gibt den Inhalt dieses Dokuments als Byte-Array zurück .

Der Quellcode des TestUtil Klasse sieht wie folgt aus:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;

import java.io.IOException;
import java.nio.charset.Charset;

public class TestUtil {

    public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));

    public static byte[] convertObjectToJsonBytes(Object object) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return mapper.writeValueAsBytes(object);
    }

    public static String createStringWithLength(int length) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < length; index++) {
            builder.append("a");
        }

        return builder.toString();
    }
}

Test 2:Aufgabeneintrag wird zur Datenbank hinzugefügt

Der zweite Komponententest stellt sicher, dass unser Controller ordnungsgemäß funktioniert, wenn ein neuer Aufgabeneintrag zur Datenbank hinzugefügt wird. Wir können diesen Test schreiben, indem wir diesen Schritten folgen:

  1. Erstellen Sie ein neues TodoDTO Objekt mit unserem Test Data Builder. Legen Sie "rechtliche" Werte für den Titel fest und Beschreibung Felder.
  2. Erstellen Sie eine Aufgabe Objekt, das zurückgegeben wird, wenn add() Methode des TodoService Schnittstelle aufgerufen wird.
  3. Konfigurieren Sie unser Mock-Objekt, um die erstellte Todo zurückzugeben Objekt, wenn es add() ist -Methode aufgerufen und ein TodoDTO Objekt wird als Parameter übergeben.
  4. Führen Sie einen POST aus Anfrage an die URL „/api/todo“. Legen Sie den Inhaltstyp der Anfrage auf „application/json“ fest. Stellen Sie den Zeichensatz der Anfrage auf „UTF-8“ ein. Transformieren Sie das erstellte TodoDTO Objekt in JSON-Bytes und senden Sie es im Hauptteil der Anfrage.
  5. Vergewissern Sie sich, dass der HTTP-Statuscode 200 zurückgegeben wird.
  6. Stellen Sie sicher, dass der Inhaltstyp der Antwort „application/json“ und ihr Inhaltstyp „UTF-8“ ist.
  7. Holen Sie sich die ID des zurückgegebenen Aufgabeneintrags mithilfe des JsonPath-Ausdrucks $.id und vergewissern Sie sich, dass die id ist 1.
  8. Holen Sie sich die Beschreibung des zurückgegebenen Aufgabeneintrags mithilfe des JsonPath-Ausdrucks $.description und vergewissern Sie sich, dass die Beschreibung ist "Beschreibung".
  9. Hol dir den Titel des zurückgegebenen Aufgabeneintrags mithilfe des JsonPath-Ausdrucks $.title und stellen Sie sicher, dass der Titel ist "Titel".
  10. Erstellen Sie einen ArgumentCaptor Objekt, das TodoDTO erfassen kann Objekte.
  11. Vergewissern Sie sich, dass add() Methode des TodoService interface wird nur einmal aufgerufen und erfasst das als Parameter übergebene Objekt.
  12. Stellen Sie sicher, dass die anderen Methoden unseres Mock-Objekts während unseres Tests nicht aufgerufen werden.
  13. Vergewissern Sie sich, dass die id des erfassten TodoDTO Objekt ist null.
  14. Vergewissern Sie sich, dass die Beschreibung des erfassten TodoDTO Objekt ist "Beschreibung".
  15. Vergewissern Sie sich, dass der Titel des erfassten TodoDTO Objekt ist "Titel".

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;

import static junit.framework.Assert.assertNull;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here.

    //The setUp() method is omitted.

    @Test
    public void add_NewTodoEntry_ShouldAddTodoEntryAndReturnAddedEntry() throws Exception {
        TodoDTO dto = new TodoDTOBuilder()
                .description("description")
                .title("title")
                .build();

        Todo added = new TodoBuilder()
                .id(1L)
                .description("description")
                .title("title")
                .build();

        when(todoServiceMock.add(any(TodoDTO.class))).thenReturn(added);

        mockMvc.perform(post("/api/todo")
                .contentType(TestUtil.APPLICATION_JSON_UTF8)
                .content(TestUtil.convertObjectToJsonBytes(dto))
        )
                .andExpect(status().isOk())
                .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.description", is("description")))
                .andExpect(jsonPath("$.title", is("title")));

        ArgumentCaptor<TodoDTO> dtoCaptor = ArgumentCaptor.forClass(TodoDTO.class);
        verify(todoServiceMock, times(1)).add(dtoCaptor.capture());
        verifyNoMoreInteractions(todoServiceMock);

        TodoDTO dtoArgument = dtoCaptor.getValue();
        assertNull(dtoArgument.getId());
        assertThat(dtoArgument.getDescription(), is("description"));
        assertThat(dtoArgument.getTitle(), is("title"));
    }
}

Zusammenfassung

Wir haben jetzt Unit-Tests für eine REST-API geschrieben, indem wir das Spring MVC Test-Framework verwendet haben. Dieses Tutorial hat uns vier Dinge beigebracht:

  • Wir haben gelernt, Unit-Tests für Controller-Methoden zu schreiben, die Informationen aus der Datenbank lesen.
  • Wir haben gelernt, Unit-Tests für Controller-Methoden zu schreiben, die der Datenbank Informationen hinzufügen.
  • Wir haben gelernt, wie wir DTO-Objekte in JSON-Bytes umwandeln und das Ergebnis der Transformation im Hauptteil der Anfrage senden können.
  • Wir haben gelernt, wie wir Assertionen für JSON-Dokumente schreiben können, indem wir JsonPath-Ausdrücke verwenden.

Die Beispielanwendung dieses Blogbeitrags erhalten Sie wie immer auf Github. Ich empfehle Ihnen, es auszuprobieren, da es viele Unit-Tests enthält, die in diesem Blogbeitrag nicht behandelt wurden.


Java-Tag