Stub (helyettesítő, tesztsegéd) — egy tesztobjektum, amely előre meghatározott válaszokat ad vissza metódushívásokra a valódi implementáció helyett. Mobilfejlesztésben a stubok elkülönítik a tesztelt modult a hálózati kérésektől, adatbázistól és fájlrendszertől, lehetővé téve a logika ellenőrzését a környezet beállítása nélkül. A mock-tól eltérően a stub nem ellenőrzi a viselkedést — csak adatokat szolgáltat. Bővebben Martin Fowler test doubles-ról szóló cikkében.
Főbb pontok
Stub — egy helyettesítő objektum, amely a tesztben helyettesíti a valódi függőséget és előre meghatározott értékeket ad vissza adott hívásokra. A kifejezést Gerard Meszaros (2007) vezette be a „xUnit Test Patterns" című könyvében. A stub a test doubles kategóriába tartozik — olyan objektumok, amelyek a tesztelés során helyettesítik a valódi komponenseket. A stub fő célja, hogy kiszámítható adatokkal lássa el a tesztelt blokkot, kiküszöbölve a külső rendszerek bizonytalanságát.
Működési elv — a teszt a végrehajtás előtt konfigurálja a stubot: „amikor a getUsers() metódus meghívásra kerül, add vissza ezt a felhasználói listát". A stub nem tartalmaz üzleti logikát, nem ellenőrzi a hívások sorrendjét, és nem rögzíti a hozzáférési előzményeket. Egyszerűen a valódi komponens helyén áll, és azt adja vissza, amit mondtak neki. Az Android tesztelés kontextusában ez azt jelenti, hogy az OkHttp kliens nem küld valódi kérést a szervernek, hanem a MockWebServer-től kap választ, amely stubként van konfigurálva.
Mikor használd — a stubok optimálisak az UI réteg (ViewModel, Presenter) és az üzleti logika (UseCase, Interactor) teszteléséhez, ahol ellenőrizni kell a reakciót adott adatokra: üres lista, a szerver 500-as hibát adott vissza, lejárt token. Minden olyan eset, ahol a teszt meghatározott bemeneti állapotot igényel, a stub feladata. Minden tesztforgatókönyvhöz saját stub konfiguráció készül, ami olvashatóvá és kiszámíthatóvá teszi a teszteket.
Gerard Meszaros (2007) a „xUnit Test Patterns" című könyvében ötféle test double-t azonosított: dummy, stub, spy, mock, fake. Minden típus a saját feladatát oldja meg. Dummy — átadásra kerül, de nem használatos. Stub — adatokat ad vissza. Spy — rögzíti a hívásokat. Mock — ellenőrzi a viselkedést. Fake — egyszerűsített logikát tartalmaz. Ennek az osztályozásnak a megértése segít a fejlesztőnek kiválasztani a megfelelő eszközt minden tesztforgatókönyvhöz.
Hálózati kérések — a stubok leggyakoribb alkalmazási forgatókönyve. Az alkalmazás HTTP-hívásokat küld az API-nak, és a tesztben ellenőrizni kell a reakciót különböző válaszokra: sikeres JSON, 401-es hiba (jogtalan), időtúllépés, üres tömb. A MockWebServer (OkHttp) Androidon és az URLProtocol (iOS) stubként működik, előre meghatározott HTTP-válaszokat adva vissza valódi szerverkapcsolat nélkül. Ez másodpercekről ezredmásodpercekre gyorsítja a teszteket.
Adatbázis — a Room (Android) és a CoreData (iOS) rendelkezik in-memory változatokkal, de ezek beállítása még mindig időt vesz igénybe. A stub a repository helyett előre elkészített Entity listákat ad vissza anélkül, hogy hozzányúlna az adatbázishoz. Ez különösen hatékony a ViewModel teszteléséhez, ahol a rendezést, szűrést vagy adatátalakítást kell ellenőrizni. A teszt ezredmásodpercek alatt fut le, függetlenül az adatok mennyiségétől.
Rendszerszolgáltatások — a LocationManager, SensorManager, SharedPreferences valódi eszközt vagy emulátort igényel. A LocationProvider stubja adott koordinátákat, a SensorManageré rögzített gyorsulásmérő értékeket ad vissza. iOS-en az analóg a CLLocationManager tesztimplementációval rendelkező delegáttal. Stubok nélkül az ilyen tesztek fizikai eszközt igényelnek meghatározott körülményekkel.
Fájlrendszer és gyorsítótár — képek betöltése, válaszok gyorsítótárazása, konfigurációs fájlokkal való munka — ezek a műveletek a lemez állapotától függenek. A FileManager vagy ImageCache stubja valódi fájlok olvasása nélkül ad vissza sikert/hibát. Ez kiküszöböli a tesztek hamis meghiúsulását az eltérő elérési utak vagy jogosultságok miatt a különböző fejlesztői gépeken.
Felelősségi körök megosztása — háromféle test double különböző feladatokat old meg. Stub: „adj nekem adatokat". Mock: „ellenőrizd, hogy hívtak-e". Fake: „úgy működöm, mint az igazi, csak egyszerűbben". A különbség kritikus a tesztek olvashatósága szempontjából: ha egy teszt mock-ot használ ott, ahol stub kellene, túlterhelődik a tesztelt forgatókönyvhöz nem kapcsolódó verify hívásokkal.
| Jellemző | Stub | Mock | Fake |
|---|---|---|---|
| Cél | Adatok szolgáltatása | Interakció ellenőrzése | Egyszerűsített implementáció |
| Logika | Nincs | Nincs | Van (de egyszerűsített) |
| Ellenőrzés | Nincs | Van (verify) | Közvetett (állapoton keresztül) |
| Rugalmasság | Alacsony — fix válaszok | Közepes | Magas — logika alkalmazkodik |
| Sebesség | Maximális | Magas | Közepes |
| Példa | MockWebServer JSON-t ad vissza | Mockito.verify(repository).save() | InMemoryRepository HashMap-mel |
Gyakorlati szabály — ha a teszt azt ellenőrzi, milyen adatokat kapott a tesztelt komponens — használj stubot. Ha a teszt azt ellenőrzi, hogy a komponens meghívta-e a függőség metódusát a megfelelő argumentumokkal — használj mock-ot. Ha egyszerűen le akarod cserélni az adatbázist egy hash táblára — ez fake. A típusok keverése egy tesztben törékennyé teszi azt: implementációváltáskor mind a stubot, mind a verify logikát át kell írni.
Stub verify-al — gyakori hiba, amikor a fejlesztő konfigurálja a stubot, majd hozzáadja a verify(stub).method()-et. A stub definíció szerint nem ellenőrizendő — az ellenőrzéshez mock van. Ha ellenőrizni kell, hogy a metódust meghatározott argumentumokkal hívták-e, használj Mockito.mock()-ot a Mockito.stub() helyett. Ez a szétválasztás világosan tartja a teszt szándékát más fejlesztők számára.
MockWebServer — OkHttp könyvtár HTTP stubok létrehozásához Androidon és JVM-en. Egy helyi HTTP szervert indít a megadott porton, amely elfogja az OkHttp kliens kéréseit és előre meghatározott válaszokat ad vissza. A beállítás három sorból áll: szerver létrehozása, válasz sorba állítása (enqueue), indítás. A teszt egymás után több választ is sorba állíthat lapozási vagy újrapróbálkozási forgatókönyvekhez.
class UserRepositoryTest {
private val server = MockWebServer()
fun setup() {
server.start(8080)
val client = OkHttpClient.Builder()
.readTimeout(1, TimeUnit.SECONDS)
.build()
}
fun test_user_list_success() {
val json = "[{\"id\":1,\"name\":\"Alice\"}]"
server.enqueue(MockResponse()
.setBody(json)
.setResponseCode(200)
)
val result = repository.getUsers()
assertEquals(1, result.size)
}
fun teardown() {
server.shutdown()
}
}
MockK — alternatíva a Mockito-hoz Kotlinhoz, first-class támogatással a korutinokhoz, kiterjesztési függvényekhez és sealed osztályokhoz. A stubok MockK-ban a coEvery (suspend függvényekhez) és az every (szokásos függvényekhez) segítségével hozhatók létre. A MockWebServer-től eltérően a MockK egyedi függőségi metódusokat helyettesít, nem a teljes HTTP réteget. Ez kényelmes a UseCase vagy Interactor egységtesztjeihez, ahol a függőségek repository absztrakciók.
interface UserRepository {
suspend fun getUsers(): List<User>
}
class GetUsersUseCaseTest {
private val repo = mockk<UserRepository>()
private val useCase = GetUsersUseCase(repo)
fun test_empty_list() = runTest {
coEvery { repo.getUsers() } returns emptyList()
val result = useCase.invoke()
assertTrue(result.isEmpty())
coVerify(exactly = 1) { repo.getUsers() }
}
}
Best practice — integrációs tesztekhez használj MockWebServer-t (elfogja a valódi HTTP-t), egységtesztekhez — MockK-t (helyettesíti az interfészeket). Ne helyettesítsd azt, amit nem tesztelsz: ha a teszt a Repository-t ellenőrzi, ne helyettesítsd benne az OkHttp klienst — használj valódi MockWebServer-t a HTTP szinten. Ez a szabály relevánsan tartja a teszteket és csökkenti a törékenységet refaktoráláskor.
Swift protokollok stubokként — az iOS natív megközelítésben a stub a függőségi protokollnak megfelelő tesztstruktúra behelyettesítésével valósul meg. A valódi NetworkService helyett a teszt StubNetworkService-t kap, amely rögzített adatokat ad vissza. A Swift statikus típusú nyelv, ezért a stub-nak ugyanannak a protokollnak kell megfelelnie, mint a valódi szolgáltatásnak. A fordító garantálja, hogy a stub implementálja az összes szükséges metódust.
protocol NetworkServiceProtocol {
func fetchUsers() async throws -> [User]
}
struct StubNetworkService: NetworkServiceProtocol {
let result: Result<[User], Error>
func fetchUsers() async throws -> [User] {
try result.get()
}
}
final class UsersViewModelTests: XCTestCase {
func test_success_state() async {
let stub = StubNetworkService(
result: .success([User(name: "Alice")])
)
let vm = UsersViewModel(service: stub)
await vm.load()
XCTAssertEqual(vm.users.count, 1)
}
}
OCMock Objective-C-hez — könyvtár stubok és mockok létrehozásához örökölt iOS projektekben. Az OCMock támogatja a stub metódusokat argumentumokkal és visszatérési értékekkel. A modern Swift projektek a protokoll-alapú megközelítést részesítik előnyben kézi stubokkal — ez minden metódus felett kontrollt ad, és nem igényel külső függőségeket. Az OCMock opció marad azon projektek számára, ahol az összes függőség protokollosítása gazdaságilag nem indokolt.
URLProtocol HTTP stubokhoz — iOS rendszermechanizmus hálózati kérések elfogására URLProtocol alosztályon keresztül. A teszt regisztrál egy egyéni URLProtocol-ot, amely elfogja az URLSession-t és stub válaszokat ad vissza. Előny a kézi stubokkal szemben: nem kell megváltoztatni az alkalmazás architektúráját — az URLSession valódi marad, de az adatok protokoll szinten cserélődnek. Hátrány: nehezebb debugolni, mint egy explicit stub szolgáltatást.
Gyakran Ismételt Kérdések
Stub előre meghatározott adatokat ad vissza, és nem ellenőrzi a hívás tényét. A Mock emellett ellenőrzi (verify), hogy a metódust a megfelelő argumentumokkal hívták-e. A Stub a „mit adjak vissza" kérdésre válaszol, a Mock — a „volt-e hívás" kérdésre. Használj stubot állapotellenőrzéshez, mock-ot interakcióellenőrzéshez.
Fake akkor kell, amikor a teszt működő (bár egyszerűsített) implementációt igényel — például in-memory adatbázist Room helyett. A Stub egyedi forgatókönyvekhez alkalmas előre meghatározott adatokkal. Ha ugyanazt a stubot ismételgeted 10 tesztben — valószínűleg Fake-re van szükséged. A Fake csökkenti az ismétlést, mert a logika egy osztályban él.
Androidon — MockK Kotlin objektumok (object) esetén támogatja a mockkObject()-et, beleértve a Java osztályok statikus metódusait a mockkStatic()-en keresztül. iOS-en — a Swift statikus metódusai nem stublolhatók közvetlenül; használj protokollokat és DI-t, hogy a static hívást a protokoll példánymetódusával helyettesítsd. A statikus stubok technikai adósságok, kerülendők az új kódban.
Használj MockWebServer-t (OkHttp) — ez egy helyi HTTP szerverként működik, amely sorba állítja (enqueue) a válaszokat. Retrofit esetén elég a base URL-t localhost:8080-ra cserélni. Ktor esetén használj MockEngine-t — a HttpStatement helyettesítésének beépített mechanizmusát. Mindkét megközelítés valódi internet nélkül működik, és teljes kontrollt ad a válasz státuszkódja, törzse és fejléce felett.
Spy beburkolja a valódi objektumot és rögzíti a hívásokat, míg a Stub teljesen lecseréli az objektumot fix válaszokra. A Spy lehetővé teszi a valódi implementáció részleges használatát (a többi metódus úgy működik, mint korábban), a stub pedig nem. Ha ellenőrizni kell, hogy egy metódust meghívtak, de a logika egy részének végre kell hajtódnia — használj spy-t, ne stubot.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is