Προγραμματισμός

Εκμάθηση JUnit 5, μέρος 2: Δοκιμή μονάδας Spring MVC με JUnit 5

Το Spring MVC είναι ένα από τα πιο δημοφιλή πλαίσια Java για την οικοδόμηση εταιρικών εφαρμογών Java και προσφέρεται πολύ για δοκιμές. Σχετικά με το σχεδιασμό, το Spring MVC προωθεί τον διαχωρισμό των ανησυχιών και ενθαρρύνει την κωδικοποίηση έναντι διεπαφών. Αυτές οι ιδιότητες, μαζί με την εφαρμογή της εξάρτησης από την Spring, κάνουν τις εφαρμογές Spring πολύ δοκιμές.

Αυτό το σεμινάριο είναι το δεύτερο μισό της εισαγωγής μου στις δοκιμές μονάδων με το JUnit 5. Θα σας δείξω πώς να ενσωματώσετε το JUnit 5 με το Spring και, στη συνέχεια, να σας παρουσιάσω τρία εργαλεία που μπορείτε να χρησιμοποιήσετε για να δοκιμάσετε τους ελεγκτές, τις υπηρεσίες και τα αποθετήρια Spring MVC.

λήψη Λήψη του κώδικα Λήψη του πηγαίου κώδικα για παράδειγμα εφαρμογές που χρησιμοποιούνται σε αυτό το σεμινάριο. Δημιουργήθηκε από τον Steven Haines για το JavaWorld.

Ενσωμάτωση JUnit 5 με Spring 5

Για αυτό το σεμινάριο, χρησιμοποιούμε το Maven και το Spring Boot, οπότε το πρώτο πράγμα που πρέπει να κάνουμε είναι να προσθέσουμε την εξάρτηση JUnit 5 στο αρχείο Maven POM:

  org.junit.jupiter junit-jupiter 5.6.0 τεστ 

Όπως και στο Μέρος 1, θα χρησιμοποιήσουμε το Mockito για αυτό το παράδειγμα. Λοιπόν, θα πρέπει να προσθέσουμε τη βιβλιοθήκη JUnit 5 Mockito:

  δοκιμή org.mockito mockito-junit-jupiter 3.2.4 

@ExtendWith και η τάξη SpringExtension

Το JUnit 5 ορίζει ένα διεπαφή επέκτασης, μέσω των οποίων τάξεις μπορούν να ενσωματωθούν με δοκιμές JUnit σε διάφορα στάδια του κύκλου ζωής εκτέλεσης. Μπορούμε να ενεργοποιήσουμε τις επεκτάσεις προσθέτοντας το @ExtendWith σχολιασμός στις τάξεις δοκιμής μας και καθορισμός της κλάσης επέκτασης που θα φορτωθεί. Η επέκταση μπορεί στη συνέχεια να εφαρμόσει διάφορες διεπαφές επανάκλησης, οι οποίες θα κληθούν σε ολόκληρο τον κύκλο ζωής δοκιμής: πριν από την εκτέλεση όλων των δοκιμών, πριν από κάθε δοκιμή, μετά από κάθε δοκιμή και μετά την εκτέλεση όλων των δοκιμών.

Η άνοιξη ορίζει α Επέκταση άνοιξη τάξη που εγγράφεται στις ειδοποιήσεις κύκλου ζωής JUnit 5 για τη δημιουργία και τη διατήρηση ενός "περιβάλλοντος δοκιμής". Θυμηθείτε ότι το πλαίσιο εφαρμογής του Spring περιέχει όλα τα Spring bean σε μια εφαρμογή και ότι εκτελεί ένεση εξάρτησης για να συνδέσει μαζί μια εφαρμογή και τις εξαρτήσεις της. Το Spring χρησιμοποιεί το μοντέλο επέκτασης JUnit 5 για να διατηρήσει το περιβάλλον εφαρμογής του τεστ, το οποίο καθιστά απλές τις δοκιμές μονάδας γραφής με το Spring.

Αφού προσθέσουμε τη βιβλιοθήκη JUnit 5 στο αρχείο Maven POM, μπορούμε να χρησιμοποιήσουμε το SpringExtension.class για να επεκτείνουμε τις τάξεις δοκιμής JUnit 5:

 @ExtendWith (SpringExtension.class) τάξη MyTests {// ...}

Το παράδειγμα, σε αυτήν την περίπτωση, είναι μια εφαρμογή Spring Boot. Ευτυχώς το @SpringBootTest Ο σχολιασμός περιλαμβάνει ήδη το @ExtendWith (SpringExtension.class) σχολιασμός, επομένως πρέπει να συμπεριλάβουμε μόνο @SpringBootTest.

Προσθήκη της εξάρτησης Mockito

Για να δοκιμάσουμε σωστά κάθε στοιχείο ξεχωριστά και να προσομοιώσουμε διαφορετικά σενάρια, θα θέλουμε να δημιουργήσουμε ψευδείς υλοποιήσεις των εξαρτήσεων κάθε τάξης. Εδώ μπαίνει το Mockito. Συμπεριλάβετε την ακόλουθη εξάρτηση στο αρχείο POM για να προσθέσετε υποστήριξη για το Mockito:

  δοκιμή org.mockito mockito-junit-jupiter 3.2.4 

Αφού ενσωματώσετε το JUnit 5 και το Mockito στην εφαρμογή Spring, μπορείτε να αξιοποιήσετε το Mockito ορίζοντας απλά ένα Spring bean (όπως μια υπηρεσία ή ένα αποθετήριο) στην τάξη δοκιμής σας χρησιμοποιώντας το @MockBean σχόλιο. Εδώ είναι το παράδειγμά μας:

 @SpringBootTest δημόσια τάξη WidgetServiceTest {/ ** * Autowire στην υπηρεσία που θέλουμε να δοκιμάσουμε * / @Autowired ιδιωτική υπηρεσία WidgetService; / ** * Δημιουργήστε μια πλαστή εφαρμογή του WidgetRepository * / @MockBean ιδιωτικό WidgetRepository repository; ...} 

Σε αυτό το παράδειγμα, δημιουργούμε μια χλεύη WidgetRepository μέσα μας Δοκιμή WidgetService τάξη. Όταν το βλέπει η Άνοιξη, θα το συνδέσει αυτόματα στο δικό μας Υπηρεσία Widget ώστε να μπορούμε να δημιουργήσουμε διαφορετικά σενάρια στις μεθόδους δοκιμών μας. Κάθε μέθοδος δοκιμής θα διαμορφώσει τη συμπεριφορά του WidgetRepository, όπως επιστρέφοντας το ζητούμενο Widget ή επιστροφή ενός Προαιρετικό.empty () για ένα ερώτημα για το οποίο δεν βρέθηκαν τα δεδομένα. Θα ξοδέψουμε το υπόλοιπο αυτού του σεμιναρίου εξετάζοντας παραδείγματα διαφόρων τρόπων διαμόρφωσης αυτών των πλαστών φασολιών.

Η εφαρμογή Spring MVC

Για να γράψουμε εαρινές δοκιμές μονάδας, χρειαζόμαστε μια εφαρμογή για να τις καταγράψουμε. Ευτυχώς, μπορούμε να χρησιμοποιήσουμε το παράδειγμα εφαρμογής από το δικό μου Σειρά άνοιξη φροντιστήριο "Mastering Spring framework 5, Μέρος 1: Spring MVC." Χρησιμοποίησα το παράδειγμα εφαρμογής από αυτό το σεμινάριο ως βασική εφαρμογή. Το τροποποίησα με ένα ισχυρότερο REST API, ώστε να έχουμε μερικά ακόμη πράγματα να δοκιμάσουμε.

Το παράδειγμα εφαρμογής είναι μια εφαρμογή ιστού Spring MVC με ελεγκτή REST, επίπεδο υπηρεσίας και αποθετήριο που χρησιμοποιεί το Spring Data JPA για να διατηρεί "widgets" από και προς μια βάση δεδομένων H2 στη μνήμη. Το σχήμα 1 είναι μια επισκόπηση.

Στίβεν Χάινς

Τι είναι το widget;

ΕΝΑ Widget είναι απλώς ένα "πράγμα" με αναγνωριστικό, όνομα, περιγραφή και αριθμό έκδοσης. Σε αυτήν την περίπτωση, το widget μας επισημαίνεται με σχολιασμούς JPA για να το ορίσει ως οντότητα. ο WidgetRestController είναι ένας ελεγκτής Spring MVC που μεταφράζει τις κλήσεις RESTful API σε ενέργειες για εκτέλεση Widgets. ο Υπηρεσία Widget είναι μια τυπική υπηρεσία Spring που καθορίζει την επιχειρησιακή λειτουργικότητα για Widgets. Τέλος, το WidgetRepository είναι μια διεπαφή Spring Data JPA, για την οποία το Spring θα δημιουργήσει μια εφαρμογή στο χρόνο εκτέλεσης. Θα εξετάσουμε τον κώδικα για κάθε τάξη καθώς γράφουμε δοκιμές στις επόμενες ενότητες.

Μονάδα δοκιμής υπηρεσίας Spring

Ας ξεκινήσουμε εξετάζοντας τον τρόπο δοκιμής μιας Άνοιξηςυπηρεσία, επειδή αυτό είναι το ευκολότερο συστατικό στην εφαρμογή MVC για δοκιμή. Παραδείγματα σε αυτήν την ενότητα θα μας επιτρέψουν να διερευνήσουμε την ενοποίηση του JUnit 5 με το Spring χωρίς να παρουσιάσουμε νέα στοιχεία δοκιμών ή βιβλιοθήκες, αν και θα το κάνουμε αργότερα στο σεμινάριο.

Θα ξεκινήσουμε εξετάζοντας το Υπηρεσία Widget διεπαφή και το WidgetServiceImpl τάξη, τα οποία εμφανίζονται στη Λίστα 1 και στη Λίστα 2, αντίστοιχα.

Λίστα 1. Η διεπαφή υπηρεσίας Spring (WidgetService.java)

 πακέτο com.geekcap.javaworld.spring5mvcexample.service; εισαγωγή com.geekcap.javaworld.spring5mvcexample.model.Widget; εισαγωγή java.util.List; εισαγωγή java.util. Προαιρετικό; δημόσια διεπαφή WidgetService {Προαιρετικό findById (Long id); Λίστα εύρεσης Όλα (); Αποθήκευση Widget (Widget widget) void deleteById (Long id); }

Λίστα 2. Η εαρινή κλάση εφαρμογής υπηρεσίας (WidgetServiceImpl.java)

 πακέτο com.geekcap.javaworld.spring5mvcexample.service; εισαγωγή com.geekcap.javaworld.spring5mvcexample.model.Widget; εισαγωγή com.geekcap.javaworld.spring5mvcexample.repository.WidgetRepository; εισαγωγή com.google.common.collect.Lists; εισαγωγή org.springframework.stereotype.Service; εισαγωγή java.util.ArrayList; εισαγωγή java.util.List; εισαγωγή java.util. Προαιρετικό; Το @Service δημόσια τάξη WidgetServiceImpl υλοποιεί το WidgetService {ιδιωτικό WidgetRepository repository; δημόσιο WidgetServiceImpl (αποθετήριο WidgetRepository) {this.repository = repository; } @Override public Προαιρετικό findById (Long id) {return repository.findById (id); } @Override δημόσια λίστα findAll () {return Lists.newArrayList (repository.findAll ()); } @Override public Widget save (Widget widget) {// Αυξήστε τον αριθμό έκδοσης widget.setVersion (widget.getVersion () + 1); // Αποθηκεύστε το widget στο repository return repository.save (widget). } @Override public void deleteById (Long id) {repository.deleteById (id); }}

WidgetServiceImpl είναι μια υπηρεσία άνοιξη, σχολιασμένη με το @Υπηρεσία σχολιασμός, που έχει WidgetRepository ενσύρματο σε αυτό μέσω του κατασκευαστή του. ο εύρεσηById (), βρείτεΌλα (), και deleteById () Οι μέθοδοι είναι όλες οι μέθοδοι διέλευσης στο υποκείμενο WidgetRepository. Η μόνη επιχειρηματική λογική που θα βρείτε βρίσκεται στο σώσει() μέθοδος, η οποία αυξάνει τον αριθμό έκδοσης του Widget όταν αποθηκεύεται.

Η τάξη δοκιμής

Για να δοκιμάσουμε αυτήν την τάξη, πρέπει να δημιουργήσουμε και να διαμορφώσουμε ένα πλαστό WidgetRepository, συνδέστε το στο WidgetServiceImpl παράδειγμα και, στη συνέχεια, συνδέστε το WidgetServiceImpl στην τάξη μας. Ευτυχώς, αυτό είναι πολύ πιο εύκολο από ό, τι ακούγεται. Η λίστα 3 δείχνει τον πηγαίο κώδικα για το Δοκιμή WidgetService τάξη.

Λίστα 3. Η τάξη δοκιμής υπηρεσίας Spring (WidgetServiceTest.java)

 πακέτο com.geekcap.javaworld.spring5mvcexample.service; εισαγωγή com.geekcap.javaworld.spring5mvcexample.model.Widget; εισαγωγή com.geekcap.javaworld.spring5mvcexample.repository.WidgetRepository; εισαγωγή org.junit.jupiter.api.Asersions; εισαγωγή org.junit.jupiter.api.DisplayName; εισαγωγή org.junit.jupiter.api.Test; εισαγωγή org.junit.jupiter.api.extension.ExtendWith; εισαγωγή org.springframework.beans.factory.annotation.Autowired; εισαγωγή org.springframework.boot.test.context.SpringBootTest; εισαγωγή org.springframework.boot.test.mock.mockito.MockBean; εισαγωγή org.springframework.test.context.junit.jupiter.SpringExtension; εισαγωγή java.util.Arrays; εισαγωγή java.util.List; εισαγωγή java.util. Προαιρετικό; εισαγωγή στατικού org.mockito.Mockito.doReturn; εισαγωγή στατικού org.mockito.ArgumentMatchers.any; @SpringBootTest δημόσια τάξη WidgetServiceTest {/ ** * Autowire στην υπηρεσία που θέλουμε να δοκιμάσουμε * / @Autowired ιδιωτική υπηρεσία WidgetService; / ** * Δημιουργήστε μια πλαστή εφαρμογή του WidgetRepository * / @MockBean ιδιωτικό WidgetRepository repository; @Test @DisplayName ("Test findById Success") void testFindById () {// Ρυθμίστε το πλαστό αποθετήριο Widget widget = νέο Widget (1l, "Widget Name", "Description", 1); doReturn (Optional.of (widget)). όταν (αποθετήριο) .findById (1l); // Εκτελέστε την κλήση υπηρεσίας Προαιρετικό returnWidget = service.findById (1l); // Επιβεβαιώστε την απόκριση Assertions.assertTrue (returnWidget.isPresent (), "Το Widget δεν βρέθηκε"); Assertions.assertSame (returnWidget.get (), widget, "Το widget που επιστράφηκε δεν ήταν το ίδιο με το mock"); } @Test @DisplayName ("Test findById Not Found") void testFindByIdNotFound () {// Ρυθμίστε το πλαστό αποθετήριο doReturn (Optional.empty ()). When (repository) .findById (1l); // Εκτελέστε την κλήση υπηρεσίας Προαιρετικό returnWidget = service.findById (1l); // Επιβεβαιώστε την απόκριση Assertions.assertFalse (returnWidget.isPresent (), "Widget δεν πρέπει να βρεθεί"); } @Test @DisplayName ("Test findAll") void testFindAll () {// Ρυθμίστε το πλαστό αποθετήριο Widget widget1 = νέο Widget (1l, "Widget Name", "Description", 1); Widget widget2 = νέο Widget (2l, "Widget 2 Name", "Περιγραφή 2", 4); doReturn (Arrays.asList (widget1, widget2)). when (αποθετήριο) .findAll (); // Εκτελέστε τη λίστα κλήσεων υπηρεσίας widgets = service.findAll (); // Επιβεβαιώστε την απόκριση Assertions.assertEquals (2, widgets.size (), "findAll shall return 2 widgets"); } @Test @DisplayName ("widget save save") void testSave () {// Ρυθμίστε το πλαστό αποθετήριο Widget widget = νέο Widget (1l, "Widget Name", "Description", 1); doReturn (widget). όταν (αποθετήριο). αποθήκευση (οποιοδήποτε ()); // Εκτελέστε την κλήση υπηρεσίας Widget ReturnWidget = service.save (widget); // Επιβεβαιώστε την απόκριση Assertions.assertNotNull (returnWidget, "Το αποθηκευμένο widget δεν πρέπει να είναι μηδενικό"); Assertions.assertEquals (2, returnWidget.getVersion (), "Η έκδοση θα πρέπει να αυξηθεί"); }} 

ο Δοκιμή WidgetService τάξη σχολιάζεται με το @SpringBootTest σχολιασμός, ο οποίος σαρώνει το CLASSPATH για όλες τις κλάσεις διαμόρφωσης Spring και φασόλια και ρυθμίζει το πλαίσιο εφαρμογής Spring για την τάξη δοκιμής. Σημειώστε ότι Δοκιμή WidgetService περιλαμβάνει επίσης σιωπηρά το @ExtendWith (SpringExtension.class) σχολιασμός, μέσω του @SpringBootTest σχολιασμός, ο οποίος ενσωματώνει την τάξη δοκιμής με το JUnit 5.

Η τάξη δοκιμής χρησιμοποιεί επίσης την άνοιξη @ Αυτόματο σχολιασμός για autowire α Υπηρεσία Widget για να δοκιμάσετε και χρησιμοποιεί το Mockito's @MockBean σχολιασμός για να δημιουργήσετε μια χλεύη WidgetRepository. Σε αυτό το σημείο, έχουμε μια χλεύη WidgetRepository που μπορούμε να διαμορφώσουμε, και ένα πραγματικό Υπηρεσία Widget με το χλεύη WidgetRepository ενσύρματο σε αυτό.

Δοκιμή της υπηρεσίας Spring

Η πρώτη μέθοδος δοκιμής, testFindById (), εκτελεί Υπηρεσία Widget'μικρό εύρεσηById () μέθοδο, η οποία θα πρέπει να επιστρέψει ένα Προαιρετικός που περιέχει ένα Widget. Αρχίζουμε δημιουργώντας ένα Widget ότι θέλουμε το WidgetRepository το να γυρίζεις. Στη συνέχεια, αξιοποιούμε το Mockito API για να διαμορφώσουμε το WidgetRepository :: findById μέθοδος. Η δομή της πλαστής λογικής μας έχει ως εξής:

 doReturn (VALUE_TO_RETURN). όταν (MOCK_CLASS_INSTANCE) .MOCK_METHOD 

Σε αυτήν την περίπτωση, λέμε: Επιστροφή Προαιρετικός των μας Widget όταν το αποθετήριο είναι εύρεσηById () Η μέθοδος καλείται με ένα όρισμα 1 (ως μακρύς).

Στη συνέχεια, επικαλούμεθα το Υπηρεσία Widget'μικρό βρείτεById μέθοδος με ένα όρισμα 1. Στη συνέχεια επικυρώνουμε ότι υπάρχει και ότι επιστρέφεται Widget είναι αυτό που διαμορφώσαμε το πλαστό WidgetRepository το να γυρίζεις.