Testing folytatás¶
Kiindulási alapnak készítsünk egy Utils
osztályt, melynek segítségével CSV-ből importálhatunk könyveket!
Ezt az osztályt fogjuk mock-olni és tesztelni.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
mock()
¶
Ezután nézzük meg, hogy hogyan tudjuk mock-olni ezt az osztályt!
Ehhez szükségünk lesz a Mockito-ra, melyhez a függőséget a pom.xml
-ben így adhatjuk meg:
1 2 3 4 5 |
|
Lássuk először az egyszerű mock-ot:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Elsőként elkészítjük a Utils
osztály egy mock objektumát a mock()
metódus meghívásával.
Ezután a when()
-el specifikáljuk az elvárásokat, azaz egy stub-ot készítünk, vagyis azt mondjuk, hogy bármi történik is, ha meghívják az importBooksFromCSV
metódust akármilyen Path
típusú paraméterrel, akkor minden esetben a fent megadott egy könyvből álló listát adja vissza.
Végül pedig teszteljük is a működését, azaz meghívjuk ezt a metódust egy tetszőleges Path
-al, majd kiírjuk az eredményt a konzolra.
Az egyszerű kiíratás helyett használjunk assert-et!
1 |
|
Most teszteljünk kivételkezelést!
1 2 3 4 5 6 7 8 9 10 |
|
A mockolással megadjuk, hogy amikor a parseBook
-ot meghívjuk, akkor dobjunk RuntimeException
-t, majd ezt teszteljük is egy assertThrows
-al.
Egy tipikus mockolási példa kialakításához hozzunk létre egy service réteget, mely belül egy BookShelf
-re tárol referenciát!
1 2 3 4 5 6 7 8 9 10 11 12 |
|
A BookShelf
osztályban szükségünk lesz a következő metódusra:
1 2 3 4 5 6 |
|
Nézzük a tesztet!
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
A fentiekkel azt teszteljük, hogy a findBookByTitle()
metódus meg lett-e hívva (tetszőleges sztringgel).
Ahhoz, hogy ez a teszt ne bukjon, ahhoz természetesen meg is kell hívnunk a findBookByTitle()
metódust.
Habár ez közvetlen nem történik meg, de a service.containsBookWithTitle("Something");
hívással közvetetten viszont igen, így a teszt át fog menni.
@Mock
¶
Amikor egy teszt osztályon belül többször használjuk a mock()
-ot ugyanarra az osztályra, akkor érdemes lehet a metódus helyett a @Mock
annotációt használni.
1 2 |
|
Ezután futtatva a tesztet, az elszáll NullPointerException
-nel.
Ez azért történik, mert a mock objektumokat a használatuk előtt inicializálnunk kell!
Az inicializálást kirakhatjuk pl. a @BeforeEach
-be:
1 2 3 4 |
|
@InjectMocks
¶
Automatikus dependency injection során igen hasznos lehet ez az annotáció. A fenti példában a következőt írtuk:
1 |
|
Ez általában felesleges boilerplate kód. Az ismétlődések elkerülése érdekében alakítsuk át a teszt osztály ide vonatkozó részeit a következőképpen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Amennyiben több ugyanolyan típusú mock objektumunk is van, akkor a @Mock
-nak adhatunk egy name
attribútumot is, mely alapján az injection végbemegy.
Spring Test¶
Tekintsünk vissza a Contacts alkalmazásunk legutóbbi verziójára.
Ebben találnunk kell egy ContactsApplicationTests
teszt osztály, amely még akkor jött létre, amikor réges-régen (egy messzi-messzi galaxisban) létrehoztuk az alkalmazást.
1 2 3 4 5 6 7 8 |
|
Itt a @SpringBootTest
annotációval megadtuk, hogy a teljes (teszt) Spring Boot kontextus töltödjön be.
Emiatt a contextLoads
teszt - ahogyan a nevéből ki is derül - most mindösszesen azt ellenőrzi, hogy ez a betöltés sikeresen végbemegy-e.
Unit Tests¶
Készítsünk egy tesztet a ContactService
-hez.
A teszt osztály váza a következőképpen fog kinézni:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Ezzel egyelőre azt mondjuk meg, hogy
- a
ContactService
bizonyos részeit injektálással mock-olni fogjuk, - a
ContactRepository
lesz az egyik dolog, amit mock-olunk és injektálunkcontactRepository
néven, valamint - a
setUp
metódusban inicializáljuk is mock-olt objektumokat.
Megjegyzés
A setUp
-ban most az initMocks
metódust használjuk.
Ez azért van, mert a Spring Initializr régebbi függőségeket húz be, mint amiket fentebb a Bookshelf-nél használtunk.
A régebbi Mockito verziókban az initMocks
, az újabbakban az openMocks
metódussal találkozhatunk.
Írjunk egy tesztet, ami a getContactById
metódust teszteli!
1 2 3 4 5 6 |
|
Ahhoz, hogy ez működjön, a setUp
-ot a következőképpen kell átalakítsuk:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Azaz meg kell adjuk, hogy amennyiben a findById
metódust bármilyen Long
típusú paraméterrel meghívják, akkor az általunk kreált Contact
objektum legyen a visszatérési érték.
Természetesen további funkciókkal bővíthetjük még a teszt osztályunkat.
Azonban, vegyük észre, hogy ebben az esetben most egy unit tesztet készítettünk, mégpedig a ContactService
-t teszteltük.
A mock-olást is pont azért csináltuk, hogy ezt az osztályt a függőségeitől függetlenül egy különálló egységként tudjuk tesztelni.
REST API Tests¶
Készítsünk tesztet a ContactRestController
-hez.
A controller-ben a contacts
és a contactById
metódusokat állítsuk vissza a legegyszerűbb működőképes verziójukra, ha esetleg az aktuális verzióban ki lennének kommentezve, vagy akár a HATEOAS átalakítások miatt nem működnének.
A teszt osztály váza az alábbi lesz:
1 2 3 4 5 6 7 8 |
|
Itt a @SpringBootTest
a context inicializálásához kell.
A MockMvc
-vel már találkoztunk korábban - ő a különböző műveletek végrehajtásához és az eredmények ellenőrzéséhez szükséges.
Az @AutoConfigureMockMvc
pedig azt biztosítja, hogy a mockMvc
-t tudjuk @Autowired
-ölni.
Valósítsuk meg a contacts
metódus tesztelését!
Ezt az alábbi kódrészlettel fogjuk elérni:
1 2 3 4 5 6 7 8 9 |
|
Info
Az egzotikusabb statikus metódusokat ezekkel az import-okkal érhetjük el:
1 2 |
|
Security Test¶
A fenti példában a /api/contact
végpont tesztelésekor egy kicsit csaltunk, ugyanis a WebSecurityConfig
-ban a REST API-t egy az egyben kiengedtük a nagyvilába: .antMatchers("/api/**", "/api-v2/**").permitAll()
Vagyis épp fordítva, a nagyvilágnak minden szűrés és megszorítás nélkül hozzáférést adtunk az API-hoz.
Mi a helyzet, akkor, ha olyan végpontokat szeretnénk tesztelni, amelyek eléréséhez authentikációra van szükség? Szimuláljuk ezt úgy, hogy az API-ra vonatkozó sort kikommentezzük a security config-ban. Próbáljuk ki, hogy mi történik ekkor!
A teszt el kell hasaljon. Az elvárt 200 OK helyett 302-es státusz kódot fog dobni és a login oldalra szeretne átirányítani minket.
A megoldáshoz szükségünk lesz először is a következő dependency-re:
1 2 3 4 5 |
|
Valamint a tesztet a következőképpen kell módosítsuk:
1 2 3 4 5 6 7 8 9 10 |
|
A megfelelő role-t és user-t a WebSecurityConfig
és a UserDataSetup
osztályokból tudjuk kinyerni.
Videó¶
A gyakorlat anyagáról készült videó: