Fake (fejk) — działająca uproszczona implementacja zależności, która zachowuje się jak prawdziwy komponent, ale wykorzystuje pamięć wewnętrzną (in-memory) lub inne lekkie mechanizmy zamiast produkcyjnej infrastruktury. W przeciwieństwie do stub, fake zawiera rzeczywistą logikę biznesową — sortowanie, filtrowanie, agregację — po prostu bez efektów zewnętrznych. In-memory baza danych zamiast Room lub HashMap zamiast SharedPreferences — klasyczne przykłady. Więcej w klasyfikacji test doubles Martina Fowlera.
Najważniejsze
Fake — to pełnowartościowa, ale uproszczona implementacja interfejsu, odpowiednia do testowania. Termin wprowadzony przez Gerarda Meszarosa (2007) w książce „xUnit Test Patterns”. W przeciwieństwie do stub, który zwraca sztywno określone odpowiedzi, fake zawiera wykonywalny kod: może sortować listę, filtrować według warunku, liczyć liczbę rekordów. Jedyna różnica od implementacji produkcyjnej — fake działa na danych in-memory i nie wykonuje rzeczywistych operacji I/O.
Główna zaleta — szybkość. Testy z fake wykonują się w milisekundach, ponieważ nie ma dostępu do dysku, sieci ani bazy danych. In-memory HashMap działa 100-1000 razy szybciej niż Room lub CoreData. Jednocześnie fake sprawdza rzeczywistą logikę biznesową: sortowanie, filtrowanie, agregację — wszystko to, czego stub nie może sprawdzić, ponieważ stub zwraca tylko to, co mu kazano. Fake daje pewność, że kod poprawnie przetwarza dane, a nie tylko otrzymuje z góry określoną odpowiedź.
Fake jest lepszy od Stub — jeśli testowany komponent wykonuje kilka operacji na danych (pobrał, przefiltrował, posortował, zapisał), stub będzie wymagał konfiguracji każdego wywołania osobno. Fake zawiera logikę wewnętrznie — test po prostu wywołuje metody i sprawdza wynik. W IT Sectr używamy fake dla wszystkich repozytoriów w testach jednostkowych: fake-repozytorium z HashMap pokrywa 90% scenariuszy bez konfiguracji Mockito czy MockK.
Kryterium wyboru — określ, co sprawdza test: stan czy interakcję. Jeśli test sprawdza stan (wynik działania) i używa logiki — potrzebujesz fake. Jeśli test potrzebuje tylko danych wejściowych bez logiki — wystarczy stub. Jeśli test sprawdza fakt wywołania metody — potrzebujesz mock. Mieszanie typów test doubles w jednym teście utrudnia zrozumienie i zwiększa kruchość.
| Kryterium | Fake | Stub | Mock |
|---|---|---|---|
| Posiadanie logiki | Tak (uproszczona) | Nie | Nie |
| Szybkość | Wysoka | Maksymalna | Wysoka |
| Sprawdzanie zachowania | Poośrednia | Nie | Tak (verify) |
| Utrzymanie | Jedna klasa na interfejs | Konfiguracja pod każdy test | Konfiguracja pod każdy test |
| Realizm | Wysoki (kod działa) | Niski (sztywne dane) | Średni |
| Ryzyko fałszywych alarmów | Niskie | Średnie | Wysokie (kruche testy) |
Antywzorzec: Fake, który nie jest fake — częsty błąd, gdy programista nazywa obiekt fake, który w rzeczywistości jest stub lub mock. Jeśli twój InMemoryUserRepository nie zawiera logiki (filtrowania, sortowania) — to nie jest fake, a stub z przechowywaniem in-memory. Fake różni się od stub właśnie posiadaniem wykonywalnej logiki. Jeśli fake-repozytorium po prostu zwraca to, co do niego włożono, i nie przetwarza danych — użyj mock lub stub.
Praktyczne zalecenie — zaczynaj od fake dla każdego repozytorium lub serwisu. Jeśli fake jest bardziej skomplikowany niż 50 linii — podziel na kilka klas. Jeśli fake w ogóle nie jest potrzebny (test sprawdza tylko jeden scenariusz z ustalonymi danymi) — użyj stub. Jeśli test sprawdza, czy metoda została wywołana z określonymi parametrami — użyj mock. Nie optymalizuj wyboru z góry: napisz fake, a jeśli okaże się nadmiarowy, zastąp go stub w konkretnym teście.
Fake-repozytorium dla Room — typowy przykład fake na Androidzie. Implementacja produkcyjna UserRepository używa Room DAO z zapytaniami SQLite. Wersja fake przechowuje dane w MutableList lub HashMap i implementuje te same metody: getUser(id), saveUser(user), deleteUser(id). Fake zawiera logikę wyszukiwania, filtrowania i sortowania — taką samą jak w repozytorium produkcyjnym, ale bez SQL. Pozwala to testować ViewModel i UseCase bez konfiguracji bazy Room.
class FakeUserRepository : UserRepository {
private val users = mutableListOf<User>()
override suspend fun getUser(id: String): User? {
return users.find { it.id == id }
}
override suspend fun saveUser(user: User) {
val index = users.indexOfFirst { it.id == user.id }
if (index >= 0) users[index] = user
else users.add(user)
}
override suspend fun search(query: String): List<User> {
return users.filter {
it.name.contains(query, ignoreCase = true)
}
}
}
Fake dla Retrofit API — zamiast MockWebServer (który jest stub, a nie fake) można utworzyć implementację ApiService, która zwraca dane z kolekcji in-memory. Różnica: MockWebServer przechwytuje HTTP i zwraca JSON, a fake-ApiService działa na poziomie interfejsu Kotlin bez serializacji. Fake jest szybszy (brak parsowania JSON) i prostszy w debugowaniu (działa w tym samym procesie, typowany). Nadaje się do testów, gdzie nie jest ważna semantyka HTTP (kody statusu, nagłówki).
Fake w Swift — buduje się przez protokoły. Klasa produkcyjna implementuje protokół z rzeczywistą logiką (CoreData, URLSession). Struktura fake implementuje ten sam protokół z pamięcią in-memory i uproszczoną logiką. Swift jest językiem z semantyką wartości, dlatego struktury fake są niemutowalne i bezpieczne w testach wielowątkowych. Daje to przewagę nad odpowiednikami na Androida: nie trzeba synchronizować dostępu do danych in-memory.
protocol UserRepositoryProtocol {
func getUser(id: String) async -> User?
func saveUser(user: User) async
}
final class FakeUserRepository: UserRepositoryProtocol {
private var storage: [String: User] = [:]
func getUser(id: String) async -> User? {
return storage[id]
}
func saveUser(user: User) async {
storage[user.id] = user
}
}
final class UserViewModelTests: XCTestCase {
func test_save_and_load() async {
let fake = FakeUserRepository()
let vm = UserViewModel(repository: fake)
let user = User(id: "1", name: "Alice")
await vm.saveUser(user)
let loaded = await vm.getUser(id: "1")
XCTAssertEqual(loaded?.name, "Alice")
}
}
Fake dla CoreData — w projektach iOS można utworzyć in-memory NSPersistentContainer, podając description.type = NSInMemoryStoreType. To pełnoprawny stos CoreData, ale działający w pamięci. Taki fake pozwala testować NSFetchRequest, predykaty i sortowania bez tworzenia pliku SQLite. Szybkość: testy na in-memory CoreData wykonują się 5-10 razy szybciej niż na odpowiedniku dyskowym. Wada: trzeba konfigurować NSManagedObjectModel za każdym razem.
FakeURLProtocol — podklasa URLProtocol do przechwytywania zapytań sieciowych na iOS. Rejestruje się przez URLProtocol.registerClass(fakeProtocol). Wewnątrz zawiera słownik in-memory URL -> Data i zwraca dane bez rzeczywistego zapytania. Różnica od stub: FakeURLProtocol może sprawdzać treść zapytania, nagłówki i zwracać różne odpowiedzi w zależności od danych wejściowych. To jest fake, ponieważ zawiera logikę routingu zapytań.
Fake jako Test Fixture — wynieś klasy fake do wspólnego modułu testowego (androidTest/sharedTest lub TestSupport). Wszystkie testy w projekcie używają tego samego InMemoryUserRepository. Eliminuje to duplikowanie konfiguracji obiektów mock w każdym teście i gwarantuje jednolite zachowanie. Zmiana logiki fake aktualizuje wszystkie testy jednocześnie. W IT Sectr przechowujemy klasy fake w sharedTest/java/com/itSectr/fake/ i podłączamy przez implementation project(:sharedTest).
Fake z predefiniowanymi danymi — często testy potrzebują repozytorium, które już zawiera pewne rekordy. Rozwiązanie: metoda fabryki fakeWithData(vararg items) lub wbudowana metoda addDefaultData(). Fabryka tworzy fake, wypełnia go typowymi danymi i zwraca gotowy do użycia obiekt. Zmniejsza to boilerplate w testach: zamiast konfiguracji wywołań mock test po prostu wywołuje FakeUserRepository.withUsers(alice, bob).
Fake z licznikiem wywołań — czasami trzeba sprawdzić nie tylko stan, ale także liczbę odwołań. Fake może zawierać liczniki: saveCallCount, getUserCallCount. Test sprawdza licznik po wykonaniu. To kompromis pomiędzy czystym fake (sprawdzanie stanu) a mock (sprawdzanie interakcji). Liczniki nie sprawdzają argumentów ani kolejności wywołań — tylko liczbę. Do sprawdzania argumentów używaj mock.
Fake z Callback — do testowania scenariuszy asynchronicznych fake może przyjmować callback przy każdym wywołaniu: beforeGetUser, afterSaveUser. Pozwala to symulować opóźnienia, błędy lub sprawdzać stany pośrednie. Takie podejście jest przydatne do testowania stanów ładowania UI: fake robi pauzę 100 ms, a test sprawdza, czy ekran pokazuje ładowarkę. W środowisku produkcyjnym callback nie występuje — to czysto testowa funkcjonalność.
Często zadawane pytania
Fake zawiera działającą logikę — filtruje, sortuje, liczy. Stub zwraca tylko z góry określone odpowiedzi bez logiki. Jeśli obiekt ma rozgałęzienia (if/else, when) — to jest fake. Jeśli zawiera tylko return values — to jest stub. Fake jest droższy w utrzymaniu, ale daje bardziej realistyczne testy.
Kiedy logika fake nie pokrywa się z logiką produkcyjną. Na przykład FakeUserRepository używa wyszukiwania case-sensitive, a produkcyjny — case-insensitive. Test przechodzi, a w rzeczywistości jest błąd. Rozwiązanie: testuj logikę fake osobno lub używaj fake tylko dla interfejsów z prostą logiką (operacje CRUD). Dla złożonej logiki pisz testy integracyjne z prawdziwą bazą danych.
In-memory database — jeden z wariantów fake. Room.inMemoryDatabaseBuilder() tworzy in-memory SQLite, który zachowuje się jak produkcyjna baza danych. To pełnoprawny fake. Ale fake może być również na poziomie repozytorium (bez SQL) i na poziomie sieci (FakeApiService). In-memory baza danych to szczególny przypadek fake, gdy logika jest maksymalnie zbliżona do rzeczywistej.
Tak, ale ostrożnie. Fake dla repozytorium (dane), Mock dla AnalyticsTracker (weryfikacja zdarzeń). Podział według warstw: fake dla warstwy danych, mock dla warstwy analityki/logowania. Nie rób jednego obiektu jednocześnie fake i mock — narusza to zasadę pojedynczej odpowiedzialności i utrudnia zrozumienie testu.
Testuj fake tymi samymi testami, co implementację produkcyjną. Jeśli masz UserRepositoryTest, który sprawdza save, get, delete — uruchom go dwukrotnie: z FakeUserRepository i z RealUserRepository. Gwarantuje to, że fake powtarza zachowanie klasy produkcyjnej. Jeśli fake zacznie działać inaczej — test nie powiedzie się na obu implementacjach.
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ż