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

Einheitentests für normale Spring MVC-Controller schreiben:Konfiguration

Im vorherigen Teil meines neuen Spring MVC Test-Tutorials haben wir gelernt, dass wir das zu testende System mithilfe der Standalone-Konfiguration konfigurieren sollten, wenn wir Komponententests für Spring MVC-Controller schreiben.

In diesem Blogbeitrag setzen wir die Theorie in die Praxis um. Dieser Blogbeitrag beschreibt, wie wir die Standalone-Konfiguration verwenden können, wenn wir Komponententests für Spring MVC-Controller schreiben, die Daten rendern und Formularübermittlungen verarbeiten.

Nachdem wir diesen Blogbeitrag fertiggestellt haben, werden wir:

  • Verstehen Sie, wie wir die erforderlichen Komponenten erstellen und konfigurieren können, ohne unserer Testsuite doppelten Code hinzuzufügen.
  • Wissen, wie wir HTTP-Anfragen an das zu testende System senden können, ohne unserer Testsuite doppelten Code hinzuzufügen.
  • Kann das Spring MVC Test Framework konfigurieren, wenn wir Unit-Tests für normale Spring MVC-Controller mit JUnit 5 schreiben.

Fangen wir an.

Einführung in das zu testende System

Das zu testende System besteht aus zwei Klassen:

  • Der TodoItemCrudController -Klasse enthält die Controller-Methoden, die die aus der Datenbank gefundenen ToDo-Elemente rendern, neue ToDo-Elemente erstellen und vorhandene ToDo-Elemente aktualisieren.
  • Der TodoItemCrudService -Klasse bietet CRUD-Vorgänge für ToDo-Elemente. Der TodoItemCrudController -Klasse ruft ihre Methoden auf, wenn sie HTTP-Anforderungen verarbeitet.

Der relevante Teil des TodoItemCrudController Klasse sieht wie folgt aus:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class TodoItemCrudController {

 private final TodoItemCrudService service;

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

Als Nächstes erstellen wir die Komponenten, die die minimale Spring MVC-Konfiguration erweitern, die erstellt wird, wenn wir das zu testende System mithilfe der eigenständigen Konfiguration konfigurieren.

Erforderliche Komponenten erstellen

Wie wir uns erinnern, sollten wir die Anzahl der benutzerdefinierten Komponenten minimieren, die wir in das zu testende System einbauen. Es kann jedoch schwierig sein, die wesentlichen Komponenten zu identifizieren, wenn wir nicht viel Erfahrung haben. Deshalb habe ich drei Regeln geschrieben, die uns bei der Auswahl der benötigten Komponenten helfen:

  • Wir sollten den verwendeten HandlerExceptionResolver konfigurieren wenn unsere Webanwendung Fehleransichten hat, die gerendert werden, wenn eine Controller-Methode eine Ausnahme auslöst.
  • Wir sollten den verwendeten LocaleResolver angeben wenn ein Locale Objekt wird als Methodenparameter in eine Methode des getesteten Controllers injiziert.
  • Wir sollten den verwendeten ViewResolver angeben wenn wir nicht möchten, dass unsere Unit-Tests den InternalResourceViewResolver verwenden das vom Spring MVC Test Framework verwendet wird, wenn kein View Resolver konfiguriert ist.

Wir können diese Komponenten erstellen und konfigurieren, indem wir diesen Schritten folgen:

Zuerst , müssen wir einen public erstellen Objektmutterklasse, die die Factory-Methoden enthält, die die erforderlichen Komponenten erstellen und konfigurieren. Nachdem wir unsere Objekt-Mutterklasse erstellt haben, müssen wir sicherstellen, dass niemand sie instanziieren kann.

Nachdem wir unsere Objekt-Mutterklasse erstellt haben, sieht ihr Quellcode wie folgt aus:


public final class WebTestConfig {

 private WebTestConfig() {}
}

Zweite , müssen wir eine Factory-Methode schreiben, die den verwendeten HandlerExceptionResolver erstellt und konfiguriert . Mit anderen Worten, wir haben einen public hinzugefügt und static Methode zum WebTestConfig Klasse. Diese Methode hat keine Methodenparameter und gibt SimpleMappingExceptionResolver zurück Objekt.

Nachdem wir diese Methode zu unserer Objekt-Mutterklasse hinzugefügt haben, müssen wir sie wie folgt implementieren:

  1. Erstellen Sie einen neuen SimpleMappingExceptionResolver Objekt.
  2. Stellen Sie sicher, dass das zu testende System die 404-Ansicht rendert, wenn TodoItemNotFoundException wird von der getesteten Controller-Methode ausgelöst.
  3. Stellen Sie sicher, dass das zu testende System die Fehleransicht rendert, wenn die getestete Controller-Methode entweder Exception auslöst oder RuntimeException .
  4. Stellen Sie sicher, dass das zu testende System den HTTP-Statuscode 404 zurückgibt, wenn es die 404-Ansicht rendert.
  5. Stellen Sie sicher, dass das zu testende System den HTTP-Statuscode 500 zurückgibt, wenn es die Fehleransicht darstellt.
  6. Gib den erstellten SimpleMappingExceptionResolver zurück Objekt.

Nachdem wir unsere Factory-Methode geschrieben haben, sieht der Quellcode unserer Objekt-Mutterklasse wie folgt aus:

import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.Properties;

public final class WebTestConfig {

 private WebTestConfig() {}

 public static SimpleMappingExceptionResolver exceptionResolver() {
 SimpleMappingExceptionResolver exceptionResolver = 
 new SimpleMappingExceptionResolver();

 Properties exceptionMappings = new Properties();

 exceptionMappings.put(
 "net.petrikainulainen.springmvctest.todo.TodoItemNotFoundException",
 "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;
 }
}

Dritter , müssen wir einen public schreiben und static Factory-Methode, die den verwendeten LocaleResolver erstellt und konfiguriert . Diese Methode hat keine Methodenparameter und gibt einen LocaleResolver zurück Objekt. Wenn wir diese Methode implementieren, müssen wir einen neuen FixedLocaleResolver zurückgeben Objekt, das Locale.ENGLISH zurückgibt . Es ist eine gute Idee, ein festes Gebietsschema zu verwenden, da es sicherstellt, dass die Umgebung, in der unsere Tests ausgeführt werden, keine falsch positiven Ergebnisse (auch Testfehler genannt) verursachen kann.

Nachdem wir unsere Factory-Methode geschrieben haben, sieht der Quellcode unserer Objekt-Mutterklasse wie folgt aus:

import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;

import java.util.Locale;
import java.util.Properties;

public final class WebTestConfig {

 private WebTestConfig() {}

 public static SimpleMappingExceptionResolver exceptionResolver() {
 SimpleMappingExceptionResolver exceptionResolver = 
 new SimpleMappingExceptionResolver();

 Properties exceptionMappings = new Properties();

 exceptionMappings.put(
 "net.petrikainulainen.springmvctest.todo.TodoItemNotFoundException",
 "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;
 }

 public static LocaleResolver fixedLocaleResolver() {
 return new FixedLocaleResolver(Locale.ENGLISH);
 }
}

Vierter , müssen wir einen public schreiben und static Factory-Methode, die den verwendeten ViewResolver erstellt und konfiguriert . Diese Methode hat keine Methodenparameter und gibt einen ViewResolver zurück Objekt. Wenn wir diese Methode implementieren, werden wir einen neuen ViewResolver erstellen (und zurückgeben). Objekt, das JSP-Ansichten verwendet.

Nachdem wir unsere Factory-Methode geschrieben haben, sieht der Quellcode unserer Objekt-Mutterklasse wie folgt aus:

import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

import java.util.Locale;
import java.util.Properties;

public final class WebTestConfig {

 private WebTestConfig() {}

 public static SimpleMappingExceptionResolver exceptionResolver() {
 SimpleMappingExceptionResolver exceptionResolver = 
 new SimpleMappingExceptionResolver();

 Properties exceptionMappings = new Properties();

 exceptionMappings.put(
 "net.petrikainulainen.springmvctest.todo.TodoItemNotFoundException",
 "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;
 }

 public static LocaleResolver fixedLocaleResolver() {
 return new FixedLocaleResolver(Locale.ENGLISH);
 }

 public static ViewResolver jspViewResolver() {
 InternalResourceViewResolver viewResolver = 
 new InternalResourceViewResolver();

 viewResolver.setViewClass(JstlView.class);
 viewResolver.setPrefix("/WEB-INF/jsp/");
 viewResolver.setSuffix(".jsp");

 return viewResolver;
 }
}

Wir können jetzt die erforderlichen Komponenten erstellen, indem wir eine Objektmutterklasse verwenden. Lassen Sie uns weitermachen und herausfinden, wie wir eine Anforderungserstellungsklasse erstellen können, die HTTP-Anforderungen an das zu testende System sendet.

Erstellen der Request Builder-Klasse

Wenn wir Komponententests für eine reale Webanwendung oder eine REST-API schreiben, stellen wir fest, dass jede Testmethode eine neue HTTP-Anforderung erstellt und an das zu testende System sendet. Dies ist eine schlechte Situation, da doppelter Code das Schreiben und Verwalten unserer Tests erschwert.

Wir können dieses Problem lösen, indem wir Request-Builder-Klassen verwenden. Eine Request-Builder-Klasse ist eine Klasse, die diese Bedingungen erfüllt:

  • Es enthält Methoden, die HTTP-Anforderungen erstellen und an das zu testende System senden, indem sie einen MockMvc verwenden Objekt.
  • Jede Methode muss einen ResultActions zurückgeben -Objekt, das es uns ermöglicht, Assertionen für die zurückgegebene HTTP-Antwort zu schreiben.

Wir können unsere Request-Builder-Klasse schreiben, indem wir diesen Schritten folgen:

  1. Neuen Kurs erstellen.
  2. Fügen Sie einen private MockMvc hinzu Feld zur erstellten Klasse. Unsere Anforderungserstellungsklasse verwendet dieses Feld, wenn sie HTTP-Anforderungen erstellt und an das zu testende System sendet.
  3. Stellen Sie sicher, dass wir den verwendeten MockMvc einfügen können Objekt in mockMvc -Feld mithilfe der Konstruktorinjektion.

Nachdem wir unsere Request-Builder-Klasse erstellt haben, sieht ihr Quellcode wie folgt aus:

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

class TodoItemRequestBuilder {

 private final MockMvc mockMvc;

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

Als nächstes werden wir lernen, das zu testende System zu konfigurieren.

Konfigurieren des zu testenden Systems

Wir können eine neue Testklasse erstellen und das zu testende System konfigurieren, indem wir diesen Schritten folgen:

Zuerst , müssen wir eine neue Testklasse erstellen und die erforderlichen Felder zu unserer Testklasse hinzufügen. Unsere Testklasse hat zwei private Felder:

  1. Der requestBuilder Feld enthält den TodoItemRequestBuilder Objekt, das von unseren Testmethoden verwendet wird, wenn sie HTTP-Anforderungen an das zu testende System senden.
  2. Die service Feld enthält einen TodoItemCrudService spotten. Unsere Einrichtungs- (und Test-) Methoden verwenden dieses Feld, wenn sie Methoden mit Mockito stubben. Außerdem verwenden unsere Testmethoden dieses Feld, wenn sie die Interaktionen überprüfen, die zwischen dem zu testenden System und unserem Mock stattgefunden haben oder nicht stattgefunden haben.

Nachdem wir unsere Testklasse erstellt haben, sieht ihr Quellcode wie folgt aus:

class TodoItemCrudControllerTest {

 private TodoItemRequestBuilder requestBuilder;
 private TodoItemCrudService service;
}

Zweite , haben wir eine neue Setup-Methode geschrieben, die ausgeführt wird, bevor eine Testmethode ausgeführt wird, und implementieren diese Methode, indem Sie diesen Schritten folgen:

  1. Erstellen Sie einen neuen TodoItemCrudService mock und speichere das erstellte Mock in service Feld.
  2. Erstellen Sie einen neuen TodoItemCrudController Objekt (das ist der getestete Controller) und das erstellte Objekt in einer lokalen Variablen speichern.
  3. Erstellen Sie einen neuen MockMvc Objekt, indem Sie die Standalone-Konfiguration verwenden und das erstellte Objekt in einer lokalen Variablen speichern. Denken Sie daran, einen benutzerdefinierten HandlerExceptionResolver zu konfigurieren , LocaleResolver und ViewResolver .
  4. Erstellen Sie einen neuen TodoItemRequestBuilder Objekt und speichern Sie das erstellte Objekt im requestBuilder Feld.

Nachdem wir unsere Setup-Methode 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.Test;
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);

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

Wir können jetzt das zu testende System konfigurieren, indem wir die Standalone-Konfiguration verwenden. Fassen wir zusammen, was wir aus diesem Blogbeitrag gelernt haben.

Zusammenfassung

Dieser Blogbeitrag hat uns Folgendes gelehrt:

  • Wir können die erforderlichen benutzerdefinierten Komponenten erstellen, ohne doppelten Code schreiben zu müssen, indem wir eine Objekt-Mutterklasse verwenden.
  • Wir können HTTP-Anfragen an das zu testende System senden, ohne doppelten Code schreiben zu müssen, indem wir eine Request-Builder-Klasse verwenden.
  • Die häufigsten benutzerdefinierten Komponenten, die im getesteten System enthalten sind, sind:HandlerExceptionResolver , LocaleResolver und ViewResolver .
  • Wenn wir das zu testende System mit der eigenständigen Konfiguration konfigurieren möchten, müssen wir den standaloneSetup() aufrufen Methode des MockMvcBuilders Klasse.
  • Wir können benutzerdefinierte Komponenten in das zu testende System einbinden, indem wir die Methoden des StandaloneMockMvcBuilder verwenden Klasse.

Java-Tag