Stub (zastępnik, imitacja) — obiekt testowy zwracający predefiniowane odpowiedzi na wywołania metod zamiast rzeczywistej implementacji. W programowaniu mobilnym staby izolują testowany moduł od zapytań sieciowych, bazy danych i systemu plików, umożliwiając sprawdzanie logiki bez konfiguracji środowiska. W przeciwieństwie do mock, stub nie weryfikuje zachowania — on tylko dostarcza dane. Więcej w artykule Martina Fowlera o test doubles.
Najważniejsze
Stub — to obiekt-zastępnik, który zastępuje rzeczywistą zależność w teście i zwraca z góry określone wartości na konkretne wywołania. Termin został wprowadzony w klasyfikacji Gerarda Meszarosa (2007) w książce „xUnit Test Patterns". Stub należy do kategorii test doubles — obiektów zastępujących rzeczywiste komponenty podczas testowania. Głównym celem staba jest dostarczenie testowanemu blokowi przewidywalnych danych, usuwając niepewność systemów zewnętrznych.
Zasada działania — test konfiguruje staba przed wykonaniem: „gdy metoda getUsers() zostanie wywołana, zwróć tę listę użytkowników". Stub nie zawiera logiki biznesowej, nie sprawdza kolejności wywołań ani nie zapisuje historii wywołań. Po prostu stoi w miejscu rzeczywistego komponentu i wydaje to, co mu kazano. W kontekście testowania Androida oznacza to, że klient OkHttp nie wykonuje rzeczywistego żądania do serwera, a otrzymuje odpowiedź z MockWebServer skonfigurowanego jako stub.
Kiedy stosować — staby są optymalne do testowania warstwy UI (ViewModel, Presenter) i logiki biznesowej (UseCase, Interactor), gdzie trzeba sprawdzić reakcję na konkretne dane: lista pusta, serwer zwrócił błąd 500, token wygasł. Każdy przypadek, w którym test wymaga określonego stanu wejściowego, to zadanie dla staba. Dla każdego scenariusza testowego tworzona jest własna konfiguracja staba, co czyni testy czytelnymi i przewidywalnymi.
Gerard Meszaros (2007) w książce „xUnit Test Patterns" wyróżnił pięć typów test doubles: dummy, stub, spy, mock, fake. Każdy typ rozwiązuje swoje zadanie. Dummy — przekazywany, ale nieużywany. Stub — zwraca dane. Spy — rejestruje wywołania. Mock — weryfikuje zachowanie. Fake — zawiera uproszczoną logikę. Zrozumienie tej klasyfikacji pomaga programiście wybrać odpowiednie narzędzie dla każdego scenariusza testowego.
Zapytania sieciowe — najczęstszy scenariusz użycia stabów. Aplikacja wykonuje wywołania HTTP do API, a w teście trzeba sprawdzić reakcję na różne odpowiedzi: udany JSON, błąd 401 (nieautoryzowany), timeout, pusta tablica. MockWebServer (OkHttp) na Androidzie i URLProtocol (iOS) pełnią rolę stabów, zwracając predefiniowane odpowiedzi HTTP bez rzeczywistego połączenia z serwerem. Przyspiesza to testy z sekund do milisekund.
Baza danych — Room (Android) i CoreData (iOS) mają warianty in-memory, ale ich konfiguracja i tak wymaga czasu. Stub zamiast repozytorium zwraca z góry przygotowane listy Entity, nie dotykając bazy danych. Jest to szczególnie skuteczne do testowania ViewModel, gdzie trzeba sprawdzić sortowanie, filtrowanie lub transformację danych. Test wykonuje się w milisekundach niezależnie od ilości danych.
Usługi systemowe — LocationManager, SensorManager, SharedPreferences wymagają rzeczywistego urządzenia lub emulatora. Stub dla LocationProvider zwraca zadane współrzędne, dla SensorManager — stałe wartości akcelerometru. Na iOS analogiem jest CLLocationManager z testową implementacją delegata. Bez stabów takie testy wymagają fizycznego urządzenia z określonymi warunkami.
System plików i pamięć podręczna — ładowanie obrazów, buforowanie odpowiedzi, praca z plikami konfiguracyjnymi — wszystkie te operacje zależą od stanu dysku. Stub dla FileManager lub ImageCache zwraca sukces/błąd bez odczytu rzeczywistych plików. Eliminuje to fałszywe upadki testów z powodu niezgodności ścieżek lub uprawnień na różnych maszynach programistów.
Podział odpowiedzialności — trzy typy test doubles rozwiązują różne zadania. Stub: „daj mi dane". Mock: „sprawdź, czy mnie wywołano". Fake: „działam jak prawdziwy, tylko prościej". Różnica jest krytyczna dla czytelności testów: jeśli test używa mock tam, gdzie potrzebny jest stub, jest przeciążony wywołaniami verify, niezwiązanymi z testowanym scenariuszem.
| Cecha | Stub | Mock | Fake |
|---|---|---|---|
| Przeznaczenie | Dostarczyć dane | Sprawdzić interakcję | Uproszczona implementacja |
| Logika | Nie | Nie | Tak (ale uproszczona) |
| Weryfikacja | Nie | Tak (verify) | Pośrednia (przez stan) |
| Elastyczność | Niska — sztywne odpowiedzi | Średnia | Wysoka — logika się dostosowuje |
| Szybkość | Maksymalna | Wysoka | Średnia |
| Przykład | MockWebServer zwraca JSON | Mockito.verify(repository).save() | InMemoryRepository z HashMap |
Praktyczna zasada — jeśli test sprawdza, jakie dane otrzymał testowany komponent — użyj stuba. Jeśli test sprawdza, czy komponent wywołał metodę zależności z prawidłowymi argumentami — użyj mocka. Jeśli po prostu chcesz zastąpić bazę danych tablicą hash — to jest fake. Mieszanie typów w jednym teście czyni go kruchym: przy zmianie implementacji trzeba będzie przepisać zarówno stuba, jak i logikę verify.
Stub z verify — częsty błąd, gdy programista konfiguruje stuba, a następnie dodaje verify(stub).method(). Stub z definicji nie powinien być weryfikowany — do weryfikacji służy mock. Jeśli musisz sprawdzić, że metoda została wywołana z konkretnymi argumentami, użyj Mockito.mock() zamiast Mockito.stub(). To rozdzielenie utrzymuje intencję testu jasną dla innych programistów.
MockWebServer — biblioteka OkHttp do tworzenia zastępników HTTP na Androidzie i JVM. Uruchamia lokalny serwer HTTP na określonym porcie, który przechwytuje żądania klienta OkHttp i zwraca predefiniowane odpowiedzi. Konfiguracja zajmuje trzy linie: utworzyć serwer, dodać odpowiedź do kolejki (enqueue), uruchomić. Test może sekwencyjnie dodawać do kolejki wiele odpowiedzi dla scenariuszy z paginacją lub ponownymi próbami.
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 — alternatywa dla Mockito dla Kotlin z natywnym wsparciem dla korutyn, funkcji rozszerzających i klas zapieczętowanych (sealed). Staby w MockK tworzy się przez coEvery (dla funkcji suspend) i every (dla zwykłych). W przeciwieństwie do MockWebServer, MockK zastępuje poszczególne metody-zależności, a nie całą warstwę HTTP. Jest to wygodne do testów jednostkowych UseCase lub Interactor, gdzie zależnościami są abstrakcje repozytoriów.
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 — do testów integracyjnych używaj MockWebServer (przechwytuje rzeczywisty HTTP), do testów jednostkowych — MockK (zastępuje interfejsy). Nie zastępuj tego, czego nie testujesz: jeśli test sprawdza Repository, nie zastępuj w nim klienta OkHttp — użyj prawdziwego MockWebServer na poziomie HTTP. Ta zasada utrzymuje testy trafne i zmniejsza ich kruchość przy refaktoryzacji.
Protokoły Swift jako staby — w natywnym podejściu iOS stub implementuje się przez podstawienie testowej struktury zgodnej z protokołem zależności. Zamiast rzeczywistego NetworkService test otrzymuje StubNetworkService, który zwraca stałe dane. Swift jest językiem ze statycznym typowaniem, więc stub musi być zgodny z tym samym protokołem co rzeczywista usługa. Kompilator gwarantuje, że stub implementuje wszystkie wymagane metody.
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 dla Objective-C — biblioteka do tworzenia stabów i mocków w starszych projektach iOS. OCMock obsługuje metody stub z argumentami i wartościami zwracanymi. Nowoczesne projekty w Swift preferują podejście oparte na protokołach z ręcznymi stubami — daje to kontrolę nad każdą metodą i nie wymaga zewnętrznych zależności. OCMock pozostaje opcją dla projektów, gdzie protokolizacja wszystkich zależności jest ekonomicznie nieuzasadniona.
URLProtocol dla zastępników HTTP — systemowy mechanizm iOS do przechwytywania zapytań sieciowych przez podklasę URLProtocol. Test rejestruje niestandardowy URLProtocol, który przechwytuje URLSession i zwraca odpowiedzi stuba. Zaleta w porównaniu z ręcznymi stubami: nie trzeba zmieniać architektury aplikacji — URLSession pozostaje rzeczywisty, ale dane są podmieniane na poziomie protokołu. Wada: trudniejsze debugowanie niż w przypadku jawnej usługi stub.
Często zadawane pytania
Stub zwraca predefiniowane dane i nie sprawdza faktu wywołania. Mock dodatkowo weryfikuje, że metoda została wywołana z prawidłowymi argumentami (verify). Stub odpowiada na pytanie „co zwrócić", Mock — na pytanie „czy było wywołanie". Używaj stuba do sprawdzania stanu, mocka — do weryfikacji interakcji.
Fake jest potrzebny, gdy test wymaga działającej (choć uproszczonej) implementacji — na przykład bazy danych in-memory zamiast Room. Stub nadaje się do pojedynczych scenariuszy z predefiniowanymi danymi. Jeśli powtarzasz ten sam stub w 10 testach — najprawdopodobniej potrzebujesz Fake. Fake zmniejsza powielanie, ponieważ logika żyje w jednej klasie.
Na Androidzie — MockK dla obiektów Kotlin (object) obsługuje mockkObject(), w tym statyczne metody klas Javy przez mockkStatic(). Na iOS — statyczne metody Swift nie są bezpośrednio zastępowalne; używaj protokołów i DI, aby zamienić wywołanie static na metodę instancyjną protokołu. Statyczne staby to dług techniczny, którego należy unikać w nowym kodzie.
Używaj MockWebServer (OkHttp) — działa jako lokalny serwer HTTP, który dodaje odpowiedzi do kolejki (enqueue). Dla Retrofit wystarczy zmienić bazowy URL na localhost:8080. Dla Ktor użyj MockEngine — wbudowanego mechanizmu do podmiany HttpStatement. Oba podejścia działają bez rzeczywistego internetu i dają pełną kontrolę nad kodem statusu, treścią i nagłówkami odpowiedzi.
Spy opakowuje rzeczywisty obiekt i rejestruje wywołania, a Stub całkowicie zastępuje obiekt stałymi odpowiedziami. Spy pozwala częściowo korzystać z rzeczywistej implementacji (pozostałe metody działają bez zmian), a stub — nie. Jeśli musisz sprawdzić, że metoda została wywołana, ale część logiki powinna się wykonać — użyj spy, a nie stuba.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również