Spring MVC er en af de mest populære Java-rammer til opbygning af Java-applikationer til virksomheder, og det egner sig meget godt til testning. Ved design fremmer Spring MVC adskillelse af bekymringer og tilskynder kodning mod grænseflader. Disse kvaliteter sammen med Spring's implementering af afhængighedsinjektion gør Spring-applikationer meget testbare.
Denne vejledning er anden halvdel af min introduktion til enhedstest med JUnit 5. Jeg viser dig, hvordan du integrerer JUnit 5 med Spring, og introducerer dig derefter til tre værktøjer, som du kan bruge til at teste Spring MVC-controllere, tjenester og opbevaringssteder.
download Hent koden Download kildekoden for eksempel applikationer, der bruges i denne vejledning. Oprettet af Steven Haines til JavaWorld.Integrering af JUnit 5 med Spring 5
Til denne vejledning bruger vi Maven og Spring Boot, så den første ting, vi skal gøre, er at tilføje JUnit 5-afhængighed til vores Maven POM-fil:
org.junit.jupiter junit-jupiter 5.6.0 test
Ligesom vi gjorde i del 1, bruger vi Mockito til dette eksempel. Så vi bliver nødt til at tilføje JUnit 5 Mockito-biblioteket:
org.mockito mockito-junit-jupiter 3.2.4 test
@ExtendWith og SpringExtension-klassen
JUnit 5 definerer en udvidelsesgrænseflade, gennem hvilke klasser kan integreres med JUnit-test på forskellige stadier af eksekveringens livscyklus. Vi kan aktivere udvidelser ved at tilføje @ExtendWith
kommentar til vores testklasser og angivelse af den udvidelsesklasse, der skal indlæses. Udvidelsen kan derefter implementere forskellige tilbagekaldelsesgrænseflader, som vil blive påberåbt gennem testlivscyklussen: før alle test kører, før hver test kører, efter hver testkørsel, og efter at alle test er kørt.
Foråret definerer en SpringExtension
klasse, der abonnerer på JUnit 5-livscyklusmeddelelser for at oprette og vedligeholde en "testkontekst." Husk på, at Spring's applikationskontekst indeholder alle Spring Beans i en applikation, og at den udfører afhængighedsinjektion for at koble en applikation og dens afhængigheder sammen. Spring bruger JUnit 5-udvidelsesmodellen til at opretholde testens applikationskontekst, hvilket gør skrivningstest med Spring ligetil.
Når vi har tilføjet JUnit 5-biblioteket til vores Maven POM-fil, kan vi bruge SpringExtension.class
at udvide vores JUnit 5 testklasser:
@ExtendWith (SpringExtension.class) klasse MyTests {// ...}
Eksemplet er i dette tilfælde en Spring Boot-applikation. Heldigvis @SpringBootTest
annotering inkluderer allerede @ExtendWith (SpringExtension.class)
kommentar, så vi behøver kun at medtage @SpringBootTest
.
Tilføjelse af Mockito-afhængighed
For at teste hver komponent korrekt isoleret og simulere forskellige scenarier, vil vi oprette mock-implementeringer af hver klasses afhængighed. Her kommer Mockito ind. Inkluder følgende afhængighed i din POM-fil for at tilføje support til Mockito:
org.mockito mockito-junit-jupiter 3.2.4 test
Når du har integreret JUnit 5 og Mockito i din Spring-applikation, kan du udnytte Mockito ved blot at definere en Spring bønne (såsom en service eller et lager) i din testklasse ved hjælp af @MockBean
kommentar. Her er vores eksempel:
@SpringBootTest offentlig klasse WidgetServiceTest {/ ** * Autowire i den tjeneste, vi vil teste * / @Autowired privat WidgetService-tjeneste; / ** * Opret en mock implementering af WidgetRepository * / @MockBean private WidgetRepository repository; ...}
I dette eksempel opretter vi en mock WidgetRepository
inde i vores WidgetServiceTest
klasse. Når foråret ser dette, vil det automatisk binde det ind i vores WidgetService
så vi kan skabe forskellige scenarier i vores testmetoder. Hver testmetode konfigurerer adfærden for WidgetRepository
, f.eks. ved at returnere det anmodede Widget
eller returnere en Valgfri. Let ()
for en forespørgsel, for hvilken data ikke findes. Vi bruger resten af denne vejledning på at se på eksempler på forskellige måder at konfigurere disse mock bønner på.
Spring MVC eksempel applikation
For at skrive fjederbaserede enhedstest har vi brug for en applikation til at skrive dem imod. Heldigvis kan vi bruge applikationseksemplet fra min Forårsserie tutorial "Mastering Spring framework 5, Part 1: Spring MVC." Jeg brugte eksempelapplikationen fra denne tutorial som en basisapplikation. Jeg ændrede det med en stærkere REST API, så vi havde et par ting mere at teste.
Eksemplet på applikationen er en Spring MVC-webapplikation med en REST-controller, et servicelag og et lager, der bruger Spring Data JPA til at fortsætte "widgets" til og fra en H2-hukommelsesdatabase. Figur 1 er en oversigt.
Steven HainesHvad er en widget?
EN Widget
er bare en "ting" med et ID, navn, beskrivelse og versionsnummer. I dette tilfælde kommenteres vores widget med JPA-kommentarer for at definere den som en enhed. Det WidgetRestController
er en Spring MVC-controller, der oversætter RESTful API-opkald til handlinger, der skal udføres på Widgets
. Det WidgetService
er en standard Spring-tjeneste, der definerer forretningsfunktionalitet for Widgets
. Endelig blev WidgetRepository
er en Spring Data JPA-grænseflade, som Spring opretter en implementering for ved kørsel. Vi gennemgår koden for hver klasse, da vi skriver prøver i de næste sektioner.
Enhedstest en forårstjeneste
Lad os starte med at gennemgå, hvordan man tester et forårservice, fordi det er den nemmeste komponent i vores MVC-applikation at teste. Eksempler i dette afsnit giver os mulighed for at udforske JUnit 5's integration med Spring uden at introducere nye testkomponenter eller biblioteker, selvom vi vil gøre det senere i vejledningen.
Vi begynder med at gennemgå WidgetService
interface og WidgetServiceImpl
klasse, som vises henholdsvis i liste 1 og liste 2.
Fortegnelse 1. Spring service interface (WidgetService.java)
pakke com.geekcap.javaworld.spring5mvcexample.service; import com.geekcap.javaworld.spring5mvcexample.model.Widget; importere java.util.List; import java.util.Optional; offentlig grænseflade WidgetService {Valgfri findById (lang id); Liste findAll (); Widget gem (Widget widget); ugyldig deleteById (lang id); }
Liste 2. Implementeringsklassen forårstjeneste (WidgetServiceImpl.java)
pakke com.geekcap.javaworld.spring5mvcexample.service; importer com.geekcap.javaworld.spring5mvcexample.model.Widget; import com.geekcap.javaworld.spring5mvcexample.repository.WidgetRepository; importer com.google.common.collect.Lists; import org.springframework.stereotype.Service; importere java.util.ArrayList; importere java.util.List; import java.util.Optional; @Service offentlig klasse WidgetServiceImpl implementerer WidgetService {privat WidgetRepository repository; offentlig WidgetServiceImpl (WidgetRepository repository) {this.repository = repository; } @ Override public Valgfri findById (lang id) {return repository.findById (id); } @ Override public List findAll () {return Lists.newArrayList (repository.findAll ()); } @Override public Widget save (Widget widget) {// Forøg versionsnummeret widget.setVersion (widget.getVersion () + 1); // Gem widget i repository return repository.save (widget); } @ Overstyr offentlig ugyldighed deleteById (Lang id) {repository.deleteById (id); }}
WidgetServiceImpl
er en forårstjeneste, kommenteret med @Service
kommentar, der har en WidgetRepository
kablet ind i det gennem sin konstruktør. Det findById ()
, findAll ()
og deleteById ()
metoder er alle gennemstrømningsmetoder til det underliggende WidgetRepository
. Den eneste forretningslogik, du finder, ligger i Gemme()
metode, der forøger versionsnummeret på Widget
når den gemmes.
Testklassen
For at teste denne klasse er vi nødt til at oprette og konfigurere en mock WidgetRepository
, træk det ind i WidgetServiceImpl
eksempel, og derefter wire WidgetServiceImpl
ind i vores testklasse. Heldigvis er det langt lettere end det lyder. Liste 3 viser kildekoden til WidgetServiceTest
klasse.
Liste 3. Spring-servicetestklassen (WidgetServiceTest.java)
pakke com.geekcap.javaworld.spring5mvcexample.service; import com.geekcap.javaworld.spring5mvcexample.model.Widget; import com.geekcap.javaworld.spring5mvcexample.repository.WidgetRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; importer org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit.jupiter.SpringExtension; importere java.util.Arrays; importere java.util.List; import java.util.Optional; importer statisk org.mockito.Mockito.doReturn; importer statisk org.mockito.ArgumentMatchers.any; @SpringBootTest offentlig klasse WidgetServiceTest {/ ** * Autowire i den service, vi vil teste * / @Autowired privat WidgetService-tjeneste; / ** * Opret en mock implementering af WidgetRepository * / @MockBean private WidgetRepository repository; @Test @DisplayName ("Test findById Success") ugyldig testFindById () {// Opsæt vores mock repository Widget widget = new Widget (1l, "Widget Name", "Description", 1); doReturn (Optional.of (widget)). når (repository) .findById (1l); // Udfør servicekaldet Valgfri returneret Widget = service.findById (1l); // Påstå svaret Assertions.assertTrue (ReturnWidget.isPresent (), "Widget blev ikke fundet"); Assertions.assertSame (ReturnWidget.get (), widget, "Den returnerede widget var ikke den samme som mocken"); } @Test @DisplayName ("Test findById ikke fundet") ugyldig testFindByIdNotFound () {// Opsæt vores mock-lager doReturn (Optional.empty ()). Når (repository) .findById (1l); // Udfør servicekaldet Valgfri returneret Widget = service.findById (1l); // Gør svaret Assertions.assertFalse (ReturnWidget.isPresent (), "Widget skal ikke findes"); } @Test @DisplayName ("Test findAll") ugyldig testFindAll () {// Opsæt vores mock repository Widget widget1 = ny Widget (1l, "Widget Name", "Description", 1); Widget widget2 = ny widget (2l, "Widget 2 navn", "Beskrivelse 2", 4); doReturn (Arrays.asList (widget1, widget2)). når (lager) .findAll (); // Udfør serviceopkaldslisten widgets = service.findAll (); // Påstå svaret Assertions.assertEquals (2, widgets.size (), "findAll skal returnere 2 widgets"); } @Test @DisplayName ("Test gem widget") ugyldig testSave () {// Opsæt vores mock repository Widget widget = new Widget (1l, "Widget Name", "Description", 1); doReturn (widget) .when (repository) .save (any ()); // Udfør servicekaldet Widget returneret Widget = service.save (widget); // Påstå svaret Assertions.assertNotNull (returneret widget, "Den gemte widget skal ikke være nul"); Assertions.assertEquals (2, ReturnWidget.getVersion (), "Versionen skal inkrementeres"); }}
Det WidgetServiceTest
klasse er kommenteret med @SpringBootTest
kommentar, der scanner CLASSPATH
for alle Spring-konfigurationsklasser og bønner og indstiller Spring-applikationskonteksten til testklassen. Noter det WidgetServiceTest
inkluderer også implicit @ExtendWith (SpringExtension.class)
kommentar gennem @SpringBootTest
kommentar, der integrerer testklassen med JUnit 5.
Testklassen bruger også Spring's @Autowired
kommentar til autotråd a WidgetService
at teste imod, og det bruger Mockito's @MockBean
kommentar for at skabe en mock WidgetRepository
. På dette tidspunkt har vi en hån WidgetRepository
at vi kan konfigurere, og en reel WidgetService
med spotten WidgetRepository
kablet ind i det.
Test af Spring Service
Den første testmetode, testFindById ()
, udfører WidgetService
's findById ()
metode, som skal returnere en Valgfri
der indeholder en Widget
. Vi begynder med at oprette en Widget
at vi vil have WidgetRepository
at vende tilbage. Vi udnytter derefter Mockito API til at konfigurere WidgetRepository :: findById
metode. Strukturen af vores mock-logik er som følger:
doReturn (VALUE_TO_RETURN) .når (MOCK_CLASS_INSTANCE) .MOCK_METHOD
I dette tilfælde siger vi: Returner en Valgfri
af vores Widget
når lageret er findById ()
metode kaldes med et argument på 1 (som en lang
).
Dernæst påberåber vi os WidgetService
's findById
metode med et argument på 1. Vi bekræfter derefter, at den er til stede, og at den returnerede Widget
er den, vi konfigurerede mocken WidgetRepository
at vende tilbage.