Programmering

JUnit 5 tutorial, del 2: Enhedstest Spring MVC med JUnit 5

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 Haines

Hvad 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.