Fake ist eine funktionierende vereinfachte Implementierung einer Abhängigkeit, die sich wie eine echte Komponente verhält, aber In-Memory-Speicher oder andere leichte Mechanismen anstelle der Produktionsinfrastruktur verwendet. Im Gegensatz zu einem Stub enthält ein Fake echte Geschäftslogik — Sortierung, Filterung, Aggregation — nur ohne externe Effekte. Eine In-Memory-Datenbank anstelle von Room oder eine HashMap anstelle von SharedPreferences sind klassische Beispiele. Weitere Details in Martin Fowlers Klassifizierung von Testdoubles.
Wichtigste Punkte
Fake ist eine vollständige, aber leichte Implementierung eines Interfaces, die sich zum Testen eignet. Der Begriff wurde von Gerard Meszaros (2007) im Buch „xUnit Test Patterns“ eingeführt. Im Gegensatz zu einem Stub, der fest codierte Antworten zurückgibt, enthält ein Fake ausführbaren Code: Er kann eine Liste sortieren, nach Bedingung filtern, Datensätze zählen. Der einzige Unterschied zur Produktionsimplementierung besteht darin, dass ein Fake mit In-Memory-Daten arbeitet und keine echten E/A-Operationen durchführt.
Der Hauptvorteil ist die Geschwindigkeit. Tests mit einem Fake werden in Millisekunden ausgeführt, da kein Zugriff auf Festplatte, Netzwerk oder Datenbank erfolgt. Eine In-Memory-HashMap arbeitet 100–1000 Mal schneller als Room oder CoreData. Gleichzeitig testet ein Fake echte Geschäftslogik: Sortierung, Filterung, Aggregation — alles, was ein Stub nicht testen kann, weil ein Stub nur das zurückgibt, was ihm gesagt wurde. Ein Fake gibt die Gewissheit, dass der Code Daten korrekt verarbeitet, anstatt nur eine vorgegebene Antwort zu erhalten.
Fake ist Stub vorzuziehen — wenn die zu testende Komponente mehrere Operationen an Daten durchführt (abrufen, filtern, sortieren, speichern), würde ein Stub die Konfiguration jedes einzelnen Aufrufs erfordern. Ein Fake enthält die Logik in sich — der Test ruft einfach Methoden auf und überprüft das Ergebnis. Bei IT Sectr verwenden wir Fakes für alle Repositories in Unit-Tests: Ein Fake-Repository mit einer HashMap deckt 90% der Szenarien ab, ohne Mockito oder MockK konfigurieren zu müssen.
Auswahlkriterium — bestimmen Sie, was der Test überprüft: Zustand oder Interaktion. Wenn der Test den Zustand (das Arbeitsergebnis) überprüft und Logik verwendet — verwenden Sie einen Fake. Wenn der Test nur Eingabedaten ohne Logik benötigt — ist ein Stub ausreichend. Wenn der Test überprüft, dass eine Methode aufgerufen wurde — verwenden Sie einen Mock. Das Mischen von Testdouble-Typen in einem Test erschwert das Verständnis und erhöht die Zerbrechlichkeit.
| Kriterium | Fake | Stub | Mock |
|---|---|---|---|
| Hat Logik | Ja (vereinfacht) | Nein | Nein |
| Geschwindigkeit | Hoch | Maximal | Hoch |
| Verhaltensüberprüfung | Indirekt | Nein | Ja (verify) |
| Wartung | Eine Klasse pro Interface | Pro Test konfigurieren | Pro Test konfigurieren |
| Realismus | Hoch (Code funktioniert) | Niedrig (feste Daten) | Mittel |
| Risiko falsch Positiver | Niedrig | Mittel | Hoch (zerbrechliche Tests) |
Antipattern: Fake, der kein Fake ist — ein häufiger Fehler, wenn ein Entwickler ein Objekt Fake nennt, das eigentlich ein Stub oder Mock ist. Wenn Ihr InMemoryUserRepository keine Logik (Filterung, Sortierung) enthält — ist es kein Fake, sondern ein Stub mit In-Memory-Speicher. Ein Fake unterscheidet sich von einem Stub genau durch das Vorhandensein ausführbarer Logik. Wenn ein Fake-Repository einfach das zurückgibt, was hineingelegt wurde, und keine Daten verarbeitet — verwenden Sie einen Mock oder Stub.
Praktische Empfehlung — beginnen Sie mit einem Fake für jedes Repository oder jeden Service. Wenn ein Fake 50 Zeilen überschreitet — teilen Sie ihn in mehrere Klassen auf. Wenn ein Fake überhaupt nicht benötigt wird (der Test überprüft nur ein einziges Szenario mit festen Daten) — verwenden Sie einen Stub. Wenn der Test überprüft, dass eine Methode mit bestimmten Parametern aufgerufen wurde — verwenden Sie einen Mock. Optimieren Sie die Wahl nicht im Voraus: Schreiben Sie einen Fake, und wenn er sich als überflüssig erweist, ersetzen Sie ihn in einem bestimmten Test durch einen Stub.
Fake-Repository für Room — ein typisches Beispiel für einen Fake auf Android. Die Produktionsimplementierung von UserRepository verwendet Room DAO mit SQLite-Abfragen. Die Fake-Version speichert Daten in einer MutableList oder HashMap und implementiert dieselben Methoden: getUser(id), saveUser(user), deleteUser(id). Der Fake enthält Such-, Filter- und Sortierlogik — genau wie das Produktions-Repository, aber ohne SQL. Dies ermöglicht das Testen von ViewModel und UseCase ohne Einrichtung einer Room-Datenbank.
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 für Retrofit-API — anstelle von MockWebServer (der ein Stub ist, kein Fake) können Sie eine ApiService-Implementierung erstellen, die Daten aus einer In-Memory-Sammlung zurückgibt. Der Unterschied: MockWebServer fängt HTTP ab und gibt JSON zurück, während ein Fake-ApiService auf der Kotlin-Interface-Ebene ohne Serialisierung arbeitet. Ein Fake ist schneller (kein JSON-Parsing) und einfacher zu debuggen (läuft im selben Prozess, typisiert). Geeignet für Tests, bei denen HTTP-Semantik (Statuscodes, Header) nicht wichtig ist.
Fake in Swift — wird über Protokolle erstellt. Die Produktionsklasse implementiert das Protokoll mit echter Logik (CoreData, URLSession). Die Fake-Struktur implementiert dasselbe Protokoll mit In-Memory-Speicher und vereinfachter Logik. Swift ist eine Sprache mit Wertsemantik, daher sind Fake-Strukturen unveränderlich und in Multithread-Tests sicher. Dies bietet einen Vorteil gegenüber Android-Äquivalenten: Der Zugriff auf In-Memory-Daten muss nicht synchronisiert werden.
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 für CoreData — in iOS-Projekten können Sie einen In-Memory-NSPersistentContainer erstellen, indem Sie description.type = NSInMemoryStoreType setzen. Dies ist ein vollständiger CoreData-Stack, der jedoch im Arbeitsspeicher läuft. Ein solcher Fake ermöglicht das Testen von NSFetchRequest, Prädikaten und Sortierungen ohne Erstellung einer SQLite-Datei. Geschwindigkeit: Tests auf In-Memory-CoreData laufen 5–10 Mal schneller als auf dem festplattenbasierten Äquivalent. Nachteil: NSManagedObjectModel muss jedes Mal eingerichtet werden.
FakeURLProtocol — eine Unterklasse von URLProtocol zum Abfangen von Netzwerkanfragen auf iOS. Es wird über URLProtocol.registerClass(fakeProtocol) registriert. Intern enthält es ein In-Memory-Wörterbuch URL -> Data und gibt Daten ohne echte Anfrage zurück. Der Unterschied zu einem Stub: FakeURLProtocol kann den Anforderungstext, Header überprüfen und je nach Eingabedaten unterschiedliche Antworten zurückgeben. Dies ist ein Fake, weil es Anforderungsrouting-Logik enthält.
Fake als Test-Fixture — legen Sie Fake-Klassen in einem gemeinsamen Testmodul ab (androidTest/sharedTest oder TestSupport). Alle Tests im Projekt verwenden dasselbe InMemoryUserRepository. Dies eliminiert die Duplizierung der Mock-Objekt-Einrichtung in jedem Test und garantiert einheitliches Verhalten. Die Änderung der Fake-Logik aktualisiert alle Tests gleichzeitig. Bei IT Sectr speichern wir Fake-Klassen in sharedTest/java/com/itSectr/fake/ und binden sie über implementation project(:sharedTest) ein.
Fake mit voreingestellten Daten — oft benötigen Tests ein Repository, das bereits einige Datensätze enthält. Lösung: Eine Factory-Methode fakeWithData(vararg items) oder eine integrierte Methode addDefaultData(). Die Factory erstellt einen Fake, füllt ihn mit typischen Daten und gibt ein gebrauchsfertiges Objekt zurück. Dies reduziert Boilerplate in Tests: Anstatt Mock-Aufrufe einzurichten, ruft der Test einfach FakeUserRepository.withUsers(alice, bob) auf.
Fake mit Aufrufzählung — manchmal muss nicht nur der Zustand, sondern auch die Anzahl der Aufrufe überprüft werden. Ein Fake kann Zähler enthalten: saveCallCount, getUserCallCount. Der Test überprüft den Zähler nach der Ausführung. Dies ist ein Kompromiss zwischen einem reinen Fake (Zustandsüberprüfung) und einem Mock (Interaktionsüberprüfung). Zähler überprüfen keine Argumente oder Aufrufreihenfolge — nur die Anzahl. Zur Argumentüberprüfung verwenden Sie einen Mock.
Fake mit Callback — zum Testen asynchroner Szenarien kann ein Fake bei jedem Aufruf einen Callback akzeptieren: beforeGetUser, afterSaveUser. Dies ermöglicht die Simulation von Verzögerungen, Fehlern oder die Überprüfung von Zwischenzuständen. Dieser Ansatz ist nützlich zum Testen von UI-Ladezuständen: Der Fake pausiert für 100 ms, und der Test überprüft, ob der Bildschirm einen Ladebalken anzeigt. Der Callback fehlt in der Produktion — dies ist reine Testfunktionalität.
Häufig gestellte Fragen
Fake enthält funktionale Logik — filtert, sortiert, zählt. Stub gibt nur vorgegebene Antworten ohne Logik zurück. Wenn ein Objekt Verzweigungen (if/else, when) hat — ist es ein Fake. Wenn es nur Rückgabewerte enthält — ist es ein Stub. Ein Fake ist wartungsintensiver, liefert aber realistischere Tests.
Wenn die Fake-Logik nicht mit der Produktionslogik übereinstimmt. Zum Beispiel verwendet FakeUserRepository eine case-sensitive Suche, während die Produktionsversion case-insensitive ist. Der Test besteht, aber in der Realität gibt es einen Fehler. Lösung: Testen Sie die Fake-Logik separat oder verwenden Sie Fakes nur für Interfaces mit einfacher Logik (CRUD-Operationen). Für komplexe Logik schreiben Sie Integrationstests mit einer echten Datenbank.
In-Memory-Datenbank ist eine Art von Fake. Room.inMemoryDatabaseBuilder() erstellt In-Memory-SQLite, das sich wie eine Produktionsdatenbank verhält. Dies ist ein vollwertiger Fake. Aber ein Fake kann auch auf Repository-Ebene (ohne SQL) und auf Netzwerkebene (FakeApiService) existieren. Eine In-Memory-Datenbank ist ein Spezialfall eines Fake, bei dem die Logik so nah wie möglich am echten Vorbild ist.
Ja, aber mit Vorsicht. Fake für das Repository (Daten), Mock für AnalyticsTracker (Ereignisprüfung). Trennung nach Schichten: Fake für die Datenschicht, Mock für die Analyse-/Protokollierungsschicht. Machen Sie ein Objekt nicht gleichzeitig zu einem Fake und einem Mock — dies verstößt gegen das Prinzip der einzigen Verantwortung und verwirrt den Test.
Testen Sie den Fake mit denselben Tests wie die Produktionsimplementierung. Wenn Sie einen UserRepositoryTest haben, der save, get, delete überprüft — führen Sie ihn zweimal aus: mit FakeUserRepository und mit RealUserRepository. Dies garantiert, dass der Fake das Verhalten der Produktionsklasse nachbildet. Wenn der Fake beginnt, sich anders zu verhalten — wird der Test bei beiden Implementierungen fehlschlagen.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch