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
ContactServicebizonyos részeit injektálással mock-olni fogjuk, - a
ContactRepositorylesz az egyik dolog, amit mock-olunk és injektálunkcontactRepositorynéven, valamint - a
setUpmetó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ó:
