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

Komponententests von Spring MVC-Controllern:Normale Controller

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

Im ersten Teil dieses Tutorials wurde beschrieben, wie wir unsere Komponententests konfigurieren können, die das Spring MVC Test Framework verwenden.

Jetzt ist es an der Zeit, sich die Hände schmutzig zu machen und zu lernen, wie wir Unit-Tests für "normale" Controller schreiben können.

Die offensichtliche nächste Frage ist

Was ist ein normaler Controller?

Nun, ein normaler Controller (im Kontext dieses Blogbeitrags) ist ein Controller, der entweder eine Ansicht rendert oder Formularübermittlungen verarbeitet.

Fangen wir an.

Erforderliche Abhängigkeiten mit Maven erhalten

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

  • Jackson 2.2.1 (Core- und Databind-Module). Wir verwenden Jackson, um Objekte in URL-codierte Strings umzuwandeln Objekte.
  • Hamcrest 1.3. Wir verwenden Hamcrest-Matcher, wenn wir Zusicherungen für die Antworten schreiben.
  • JUnit 4.11 (ausgenommen die Hamcrest-Core-Abhängigkeit).
  • Mockito 1.9.5
  • Frühjahrstest 3.2.3.FREIGABE

Der relevante Teil unserer pom.xml Datei sieht wie folgt aus:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.2.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.2.1</version>
    <scope>test</scope>
</dependency>
<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>

Fahren wir fort und finden heraus, wie wir Komponententests für Spring MVC-Controller schreiben können, indem wir das Spring MVC Test-Framework verwenden.

Einheitentests für Controller-Methoden schreiben

Jeder Unit-Test, den wir schreiben, um das Verhalten einer Controller-Methode zu testen, besteht aus diesen Schritten:

  1. Wir senden eine Anfrage an die getestete Controller-Methode.
  2. Wir bestätigen, dass wir die erwartete Antwort erhalten haben.

Das Spring MVC Test Framework verfügt über einige „Kern“-Klassen, die wir zur Implementierung dieser Schritte in unseren Tests verwenden können. Diese Klassen werden im Folgenden beschrieben:

  • Wir können unsere Anfragen erstellen, indem wir die statischen Methoden von MockMvcRequestBuilders verwenden Klasse. Genauer gesagt, wir können Request-Builder erstellen, die dann als Methodenparameter an die Methode übergeben werden, die die eigentliche Anfrage ausführt.
  • Der MockMvc Klasse ist der Haupteinstiegspunkt unserer Tests. Wir können Anfragen ausführen, indem wir seinen perform(RequestBuilder requestBuilder) aufrufen Methode.
  • Wir können Zusicherungen für die empfangene Antwort schreiben, indem wir die statischen Methoden der MockMvcResultMatchers verwenden Klasse.

Als Nächstes werfen wir einen Blick auf einige Beispiele, die zeigen, wie wir diese Klassen in unseren Unit-Tests verwenden können. Wir werden Unit-Tests für die folgenden Controller-Methoden schreiben:

  • Die erste Controller-Methode rendert eine Seite, die eine Liste von Todo-Einträgen zeigt.
  • Die zweite Controller-Methode rendert eine Seite, die die Informationen eines einzelnen Aufgabeneintrags anzeigt.
  • Die dritte Controller-Methode verarbeitet Formularübermittlungen des Formulars, das zum Hinzufügen neuer Aufgabeneinträge zur Datenbank verwendet wird.

Darstellung der Todo-Eintragslistenseite

Beginnen wir mit einem Blick auf die Implementierung der Controller-Methode, die zum Rendern der Todo-Eintragslistenseite verwendet wird.

Erwartetes Verhalten

Die Implementierung der Controller-Methode, die verwendet wird, um die Informationen aller ToDo-Einträge anzuzeigen, hat die folgenden Schritte:

  1. Es verarbeitet GET Anfragen werden an URL '/' gesendet.
  2. Es ruft die Aufgabeneinträge ab, indem es findAll() aufruft Methode des TodoService Schnittstelle. Diese Methode gibt eine Liste von Todo zurück Objekte.
  3. Es fügt die empfangene Liste zum Modell hinzu.
  4. Er gibt den Namen der gerenderten Ansicht zurück.

Der relevante Teil des TodoController Klasse sieht wie folgt aus:

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@Controller
public class TodoController {

    private final TodoService service;
    
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String findAll(Model model) {
        List<Todo> models = service.findAll();
        model.addAttribute("todos", models);
        return "todo/list";
    }
}

Wir sind jetzt bereit, einen Komponententest für diese Methode zu schreiben. Mal sehen, wie wir das machen können.

Test:Aufgabeneinträge gefunden

Wir können einen Einheitentest für diese Controller-Methode schreiben, indem wir die folgenden Schritte ausführen:

  1. Erstellen Sie die Testdaten, die zurückgegeben werden, wenn unsere Servicemethode aufgerufen wird. Wir verwenden ein Konzept namens Test Data Builder, wenn wir die Testdaten für unseren Test erstellen.
  2. Konfigurieren Sie das verwendete Mock-Objekt 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 '/'.
  4. Stellen Sie sicher, dass der HTTP-Statuscode 200 zurückgegeben wird.
  5. Stellen Sie sicher, dass der Name der zurückgegebenen Ansicht „todo/list“ lautet.
  6. Stellen Sie sicher, dass die Anfrage an die URL '/WEB-INF/jsp/todo/list.jsp' weitergeleitet wird.
  7. Stellen Sie sicher, dass das Modellattribut todos genannt wird enthält zwei Elemente.
  8. Stellen Sie sicher, dass das Modellattribut namens todos enthält die richtigen Elemente.
  9. Vergewissern Sie sich, dass findAll() Methode unseres Scheinobjekts wurde nur einmal aufgerufen.
  10. Stellen Sie sicher, dass während des Tests keine anderen Methoden des Scheinobjekts aufgerufen wurden.

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
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 org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.Arrays;

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

@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_ShouldAddTodoEntriesToModelAndRenderTodoListView() 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("/"))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/list"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/list.jsp"))
                .andExpect(model().attribute("todos", hasSize(2)))
                .andExpect(model().attribute("todos", hasItem(
                        allOf(
                                hasProperty("id", is(1L)),
                                hasProperty("description", is("Lorem ipsum")),
                                hasProperty("title", is("Foo"))
                        )
                )))
                .andExpect(model().attribute("todos", hasItem(
                        allOf(
                                hasProperty("id", is(2L)),
                                hasProperty("description", is("Lorem ipsum")),
                                hasProperty("title", is("Bar"))
                        )
                )));

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

Rendern der Todo-Eingabeseite anzeigen

Bevor wir die eigentlichen Unit-Tests für unsere Controller-Methode schreiben können, müssen wir uns die Implementierung dieser Methode genauer ansehen.

Lassen Sie uns weitermachen und herausfinden, wie unser Controller implementiert ist.

Erwartetes Verhalten

Die Controller-Methode, die verwendet wird, um die Informationen eines einzelnen Aufgabeneintrags anzuzeigen, wird folgendermaßen implementiert:

  1. Es verarbeitet GET Anfragen werden an die URL „/todo/{id}“ gesendet. Die {id} ist eine Pfadvariable, die die ID des angeforderten Aufgabeneintrags enthält.
  2. Es erhält den angeforderten Todo-Eintrag, indem es findById() aufruft Methode des TodoService -Schnittstelle und übergibt die ID des angeforderten Aufgabeneintrags als Methodenparameter. Diese Methode gibt den gefundenen Aufgabeneintrag zurück. Wenn kein Aufgabeneintrag gefunden wird, löst diese Methode eine TodoNotFoundException aus .
  3. Es fügt den gefundenen Aufgabeneintrag zum Modell hinzu.
  4. Er gibt den Namen der gerenderten Ansicht zurück.

Der Quellcode unserer Controller-Methode sieht wie folgt aus:

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

@Controller
public class TodoController {

    private final TodoService service;

    @RequestMapping(value = "/todo/{id}", method = RequestMethod.GET)
    public String findById(@PathVariable("id") Long id, Model model) throws TodoNotFoundException {
        Todo found = service.findById(id);
        model.addAttribute("todo", found);
        return "todo/view";
    }
}

Unsere nächste Frage lautet:

Was passiert, wenn eine TodoNotFoundException ausgelöst wird?

Im vorherigen Teil dieses Tutorials haben wir eine Ausnahme-Resolver-Bean erstellt, die verwendet wird, um Ausnahmen zu behandeln, die von unseren Controller-Klassen ausgelöst werden. Die Konfiguration dieser Bean sieht wie folgt aus:

@Bean
public SimpleMappingExceptionResolver exceptionResolver() {
	SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

	Properties exceptionMappings = new Properties();

	exceptionMappings.put("net.petrikainulainen.spring.testmvc.todo.exception.TodoNotFoundException", "error/404");
	exceptionMappings.put("java.lang.Exception", "error/error");
	exceptionMappings.put("java.lang.RuntimeException", "error/error");

	exceptionResolver.setExceptionMappings(exceptionMappings);

	Properties statusCodes = new Properties();

	statusCodes.put("error/404", "404");
	statusCodes.put("error/error", "500");

	exceptionResolver.setStatusCodes(statusCodes);

	return exceptionResolver;
}

Wie wir sehen können, wenn eine TodoNotFoundException ausgelöst wird, rendert unsere Anwendung die Ansicht „error/404“ und gibt den HTTP-Statuscode 404 zurück.

Es ist klar, dass wir für diese Controller-Methode zwei Tests schreiben müssen:

  1. Wir müssen einen Test schreiben, der sicherstellt, dass unsere Anwendung korrekt funktioniert, wenn der Todo-Eintrag nicht gefunden wird.
  2. Wir müssen einen Test schreiben, der überprüft, ob unsere Anwendung korrekt funktioniert, 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 funktionsfähig ist, wenn der angeforderte Aufgabeneintrag nicht gefunden wird. Wir können den Test schreiben, der dies sicherstellt, indem wir diesen Schritten folgen:

  1. Konfigurieren Sie das Scheinobjekt so, dass es eine TodoNotFoundException auslöst wenn es findById() ist -Methode aufgerufen wird und die ID des angeforderten Aufgabeneintrags 1L ist.
  2. Führen Sie ein GET aus Anfrage an URL '/todo/1'.
  3. Vergewissern Sie sich, dass der HTTP-Statuscode 404 zurückgegeben wird.
  4. Stellen Sie sicher, dass der Name der zurückgegebenen Ansicht „error/404“ lautet.
  5. Stellen Sie sicher, dass die Anfrage an die URL '/WEB-INF/jsp/error/404.jsp' weitergeleitet wird.
  6. Vergewissern Sie sich, dass die findById() Methode des TodoService Schnittstelle wird nur einmal mit dem richtigen Methodenparameter (1L) aufgerufen.
  7. Stellen Sie sicher, dass während dieses Tests keine anderen Methoden des Mock-Objekts aufgerufen wurden.

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
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 org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
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_ShouldRender404View() throws Exception {
        when(todoServiceMock.findById(1L)).thenThrow(new TodoNotFoundException(""));

        mockMvc.perform(get("/todo/{id}", 1L))
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));

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

Test 2:Aufgabeneintrag gefunden

Zweitens müssen wir einen Test schreiben, der sicherstellt, dass unser Controller ordnungsgemäß funktioniert, wenn ein Todo-Eintrag gefunden wird. Wir können dies tun, indem wir diesen Schritten folgen:

  1. Erstellen Sie die Todo Objekt, das zurückgegeben wird, wenn unsere Service-Methode aufgerufen wird. Auch hier erstellen wir das zurückgegebene Todo 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 '/todo/1'.
  5. Vergewissern Sie sich, dass der HTTP-Statuscode 200 zurückgegeben wird.
  6. Stellen Sie sicher, dass der Name der zurückgegebenen Ansicht „todo/view“ lautet.
  7. Stellen Sie sicher, dass die Anfrage an die URL '/WEB-INF/jsp/todo/view.jsp' weitergeleitet wird.
  8. Vergewissern Sie sich, dass die id des Modellobjekts namens todo ist 1L.
  9. Vergewissern Sie sich, dass die Beschreibung des Modellobjekts namens todo ist 'Lorem ipsum'.
  10. Vergewissern Sie sich, dass der Titel des Modellobjekts namens todo ist 'Foo'.
  11. Stellen Sie sicher, dass die findById() Methode unseres Mock-Objekts wird nur einmal mit dem richtigen Methodenparameter (1L) aufgerufen.
  12. Stellen Sie sicher, dass die anderen Methoden des Mock-Objekts während unseres Tests nicht aufgerufen wurden.

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
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 org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
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_TodoEntryFound_ShouldAddTodoEntryToModelAndRenderViewTodoEntryView() throws Exception {
        Todo found = new TodoBuilder()
                .id(1L)
                .description("Lorem ipsum")
                .title("Foo")
                .build();

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

        mockMvc.perform(get("/todo/{id}", 1L))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/view"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/view.jsp"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", is("Lorem ipsum"))))
                .andExpect(model().attribute("todo", hasProperty("title", is("Foo"))));

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

Handhabung der Formularübermittlung des Todo-Eintragsformulars

Auch hier werfen wir zunächst einen Blick auf das erwartete Verhalten unserer Controller-Methode, bevor wir die Unit-Tests dafür schreiben.

Erwartetes Verhalten

Die Controller-Methode, die die Formularübermittlungen des Eingabeformulars zum Hinzufügen von Aufgaben handhabt, wird folgendermaßen implementiert:

  1. Es verarbeitet POST Anfragen werden an die URL '/todo/add' gesendet.
  2. Es prüft, ob das BindingResult Das als Methodenparameter angegebene Objekt weist keine Fehler auf. Wenn Fehler gefunden werden, wird der Name der Formularansicht zurückgegeben.
  3. Es fügt einen neuen Todo-Eintrag hinzu, indem es add() aufruft Methode des TodoService -Schnittstelle und übergibt das Formularobjekt als Methodenparameter. Diese Methode erstellt einen neuen Aufgabeneintrag und gibt ihn zurück.
  4. Es erstellt die Feedback-Nachricht über den hinzugefügten Aufgabeneintrag und fügt die Nachricht zu den RedirectAttributes hinzu Objekt als Methodenparameter angegeben.
  5. Es fügt die ID des hinzugefügten Aufgabeneintrags zu den RedirectAttributes hinzu Objekt.
  6. Er gibt den Namen einer Umleitungsansicht zurück, die die Anforderung auf die Seite zur Eingabe der Todo-Ansicht umleitet.

Der relevante Teil des TodoController Klasse sieht wie folgt aus:

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

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

@Controller
@SessionAttributes("todo")
public class TodoController {

    private final TodoService service;

    private final MessageSource messageSource;

    @RequestMapping(value = "/todo/add", method = RequestMethod.POST)
    public String add(@Valid @ModelAttribute("todo") TodoDTO dto, BindingResult result, RedirectAttributes attributes) {
        if (result.hasErrors()) {
            return "todo/add";
        }

        Todo added = service.add(dto);

        addFeedbackMessage(attributes, "feedback.message.todo.added", added.getTitle());
        attributes.addAttribute("id", added.getId());

        return createRedirectViewPath("/todo/{id}");
    }

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

    private String getMessage(String messageCode, Object... messageParameters) {
        Locale current = LocaleContextHolder.getLocale();
        return messageSource.getMessage(messageCode, messageParameters, current);
    }

    private String createRedirectViewPath(String requestMapping) {
        StringBuilder redirectViewPath = new StringBuilder();
        redirectViewPath.append("redirect:");
        redirectViewPath.append(requestMapping);
        return redirectViewPath.toString();
    }
}

Wie wir sehen können, verwendet die Controller-Methode ein TodoDTO Objekt als Formularobjekt. 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.
}

Das TodoDTO Die Klasse deklariert einige Validierungseinschränkungen, die im Folgenden beschrieben werden:

  • Der Titel eines Aufgabeneintrags darf nicht leer sein.
  • Die maximale Länge der Beschreibung beträgt 500 Zeichen.
  • Die maximale Länge des Titels beträgt 100 Zeichen.

Wenn wir an die Tests denken, die wir für diese Controller-Methode schreiben sollten, ist klar, dass wir dafür sorgen müssen

  1. Die Controller-Methode funktioniert, wenn die Validierung fehlschlägt.
  2. Die Controller-Methode ist eine Arbeitseigenschaft, wenn ein Aufgabeneintrag zur Datenbank hinzugefügt wird.

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

Test 1:Validierung schlägt fehl

Zuerst müssen wir einen Test schreiben, der sicherstellt, dass unsere Controller-Methode ordnungsgemäß funktioniert, wenn die Validierung 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. Führen Sie einen POST aus Fordern Sie die URL „/todo/add“ an, indem Sie die folgenden Schritte ausführen:
    1. Setzen Sie den Inhaltstyp der Anfrage auf „application/x-www-form-urlencoded“.
    2. Senden Sie die Beschreibung und Titel des todo-Eintrags als Anfrageparameter.
    3. Legen Sie ein neues TodoDTO fest Sitzung widersprechen. Dies ist erforderlich, da unser Controller mit den @SessionAttributes annotiert ist Anmerkung.
  4. Vergewissern Sie sich, dass der HTTP-Statuscode 200 zurückgegeben wird.
  5. Vergewissern Sie sich, dass der Name der zurückgegebenen Ansicht "todo/add" lautet.
  6. Vergewissern Sie sich, dass die Anfrage an die URL „/WEB-INF/jsp/todo/add.jsp“ weitergeleitet wird.
  7. Vergewissern Sie sich, dass unser Modellattribut Feldfehler im Titel aufweist und Beschreibung Felder.
  8. Stellen Sie sicher, dass die id unseres Modellattributs ist null.
  9. Stellen Sie sicher, dass die Beschreibung unseres Modellattributs ist korrekt.
  10. Stellen Sie sicher, dass der Titel unseres Modellattributs ist korrekt.
  11. Stellen Sie sicher, dass die Methoden unseres Scheinobjekts während des Tests nicht aufgerufen wurden.

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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 org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
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 add_DescriptionAndTitleAreTooLong_ShouldRenderFormViewAndReturnValidationErrorsForTitleAndDescription() throws Exception {
        String title = TestUtil.createStringWithLength(101);
        String description = TestUtil.createStringWithLength(501);

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .param("description", description)
                .param("title", title)
                .sessionAttr("todo", new TodoDTO())
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/add"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attributeHasFieldErrors("todo", "description"))
                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
                .andExpect(model().attribute("todo", hasProperty("description", is(description))))
                .andExpect(model().attribute("todo", hasProperty("title", is(title))));

        verifyZeroInteractions(todoServiceMock);
    }
}

Unser Testfall ruft das statische createStringWithLength(int length) auf Methode des TestUtil Klasse. Diese Methode erstellt einen neuen String Objekt mit der angegebenen Länge und gibt das erstellte Objekt zurück.

Der Quellcode des TestUtil Klasse sieht wie folgt aus:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class TestUtil {

    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

Zweitens müssen wir einen Test schreiben, der sicherstellt, 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 eine Aufgabe Objekt, das zurückgegeben wird, wenn add() Methode des TodoService Schnittstelle aufgerufen wird.
  2. Konfigurieren Sie unser Mock-Objekt, um die erstellte Todo zurückzugeben Objekt, wenn es add() ist -Methode wird als TodoDTO bezeichnet Objekt wird als Methodenparameter angegeben.
  3. Führen Sie einen POST aus Fordern Sie die URL „/todo/add“ an, indem Sie die folgenden Schritte ausführen:
    1. Setzen Sie den Inhaltstyp der Anfrage auf „application/x-www-form-urlencoded“.
    2. Senden Sie die Beschreibung und Titel des todo-Eintrags als Anfrageparameter.
    3. Legen Sie ein neues TodoDTO fest Sitzung widersprechen. Dies ist erforderlich, da unser Controller mit den @SessionAttributes annotiert ist Anmerkung.
  4. Vergewissern Sie sich, dass der HTTP-Statuscode 302 zurückgegeben wird.
  5. Stellen Sie sicher, dass der Name der zurückgegebenen Ansicht „redirect:todo/{id}“ lautet.
  6. Stellen Sie sicher, dass die Anfrage an die URL „/todo/1“ umgeleitet wird.
  7. Vergewissern Sie sich, dass das Modellattribut namens id ist '1'.
  8. Vergewissern Sie sich, dass die Feedback-Nachricht eingestellt ist.
  9. Vergewissern Sie sich, dass add() Methode unseres Mock-Objekts nur einmal aufgerufen wird und zwar ein TodoDTO Objekt wurde als Methodenparameter angegeben. Erfassen Sie das als Methodenparameter angegebene Objekt mit einem ArgumentCaptor Objekt.
  10. Stellen Sie sicher, dass während unseres Tests keine anderen Methoden des Mock-Objekts aufgerufen wurden.
  11. Überprüfen Sie, ob die Feldwerte von TodoDTO Objekt sind korrekt.

Der Quellcode unseres Unit-Tests sieht wie folgt aus:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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 org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
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 add_NewTodoEntry_ShouldAddTodoEntryAndRenderViewTodoEntryView() throws Exception {
        Todo added = new TodoBuilder()
                .id(1L)
                .description("description")
                .title("title")
                .build();

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

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .param("description", "description")
                .param("title", "title")
                .sessionAttr("todo", new TodoDTO())
        )
                .andExpect(status().isMovedTemporarily())
                .andExpect(view().name("redirect:todo/{id}"))
                .andExpect(redirectedUrl("/todo/1"))
                .andExpect(model().attribute("id", is("1")))
                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: title was added.")));

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

		TodoDTO formObject = formObjectArgument.getValue();

		assertThat(formObject.getDescription(), is("description"));
		assertNull(formObject.getId());
		assertThat(formObject.getTitle(), is("title"));
    }
}

Zusammenfassung

Wir haben nun einige Unit-Tests für "normale" Controller-Methoden geschrieben, indem wir das Spring MVC Test-Framework verwendet haben. Dieses Tutorial hat vier Dinge gelehrt:

  • Wir haben gelernt, Anfragen zu erstellen, die von den getesteten Controller-Methoden verarbeitet werden.
  • Wir haben gelernt, Zusicherungen für die Antworten zu schreiben, die von den getesteten Controller-Methoden zurückgegeben werden.
  • Wir haben gelernt, wie wir Unit-Tests für Controller-Methoden schreiben können, die eine Ansicht rendern.
  • Wir haben gelernt, Unit-Tests für Controller-Methoden zu schreiben, die Formularübermittlungen handhaben.

Der nächste Teil dieses Tutorials beschreibt, wie wir Komponententests für eine REST-API schreiben können.

P.S. Sie können die Beispielanwendung dieses Blogbeitrags von Github erhalten. Ich empfehle Ihnen, es auszuprobieren, da es einige Unit-Tests enthält, die in diesem Blogbeitrag nicht behandelt wurden.


Java-Tag