Fake (feek) — een werkende vereenvoudigde implementatie van een afhankelijkheid die zich gedraagt als een echte component, maar in-memory opslag of andere lichte mechanismen gebruikt in plaats van productie-infrastructuur. In tegenstelling tot stub bevat fake echte bedrijfslogica — sorteren, filteren, aggregeren — alleen zonder externe effecten. In-memory database in plaats van Room of HashMap in plaats van SharedPreferences — klassieke voorbeelden. Meer in classificatie van test doubles door Martin Fowler.
Belangrijkste
Fake — een volledige, maar lichte implementatie van een interface, geschikt voor testen. De term is geïntroduceerd door Gerard Meszaros (2007) in het boek «xUnit Test Patterns». In tegenstelling tot stub, die strikt vooraf bepaalde antwoorden retourneert, bevat fake uitvoerbare code: het kan een lijst sorteren, filteren op voorwaarde, het aantal records tellen. Het enige verschil met de productie-implementatie — fake werkt met in-memory gegevens en voert geen echte I/O-bewerkingen uit.
Belangrijkste voordeel — snelheid. Testen met fake worden in milliseconden uitgevoerd, omdat er geen toegang is tot schijf, netwerk of database. In-memory HashMap werkt 100-1000 keer sneller dan Room of CoreData. Tegelijkertijd controleert fake echte bedrijfslogica: sorteren, filteren, aggregeren — alles wat stub niet kan controleren, omdat stub alleen retourneert wat hem is verteld. Fake geeft vertrouwen dat de code gegevens correct verwerkt, niet alleen een vooraf bepaald antwoord ontvangt.
Fake heeft de voorkeur boven Stub — als de geteste component meerdere bewerkingen op gegevens uitvoert (opgehaald, gefilterd, gesorteerd, opgeslagen), vereist stub configuratie van elke aanroep afzonderlijk. Fake bevat de logica intern — de test roept eenvoudig methoden aan en controleert het resultaat. Bij IT Sectr gebruiken we fake voor alle repositories in unit tests: fake-repository met HashMap dekt 90% van scenario's zonder configuratie van Mockito of MockK.
Selectiecriterium — bepaal wat de test controleert: status of interactie. Als de test status (het resultaat) controleert en logica gebruikt — is fake nodig. Als de test alleen invoergegevens zonder logica nodig heeft — is stub voldoende. Als de test het feit van een methode-aanroep controleert — is mock nodig. Het mengen van test doubles types in één test bemoeilijkt het begrip en verhoogt de kwetsbaarheid.
| Criterium | Fake | Stub | Mock |
|---|---|---|---|
| Aanwezigheid van logica | Ja (vereenvoudigd) | Nee | Nee |
| Snelheid | Hoog | Maximaal | Hoog |
| Gedragscontrole | Indirect | Nee | Ja (verify) |
| Onderhoud | Één klasse per interface | Configuratie per test | Configuratie per test |
| Realisme | Hoog (code werkt) | Laag (vaste gegevens) | Gemiddeld |
| Risico op valse alarmen | Laag | Gemiddeld | Hoog (kwetsbare testen) |
Antipatroon: Fake die geen fake is — een veelgemaakte fout waarbij een ontwikkelaar een object fake noemt dat eigenlijk stub of mock is. Als InMemoryUserRepository geen logica (filteren, sorteren) bevat — is dit geen fake, maar stub met in-memory opslag. Fake verschilt van stub juist door de aanwezigheid van uitvoerbare logica. Als een fake-repository gewoon retourneert wat erin is gestopt en geen gegevens verwerkt — gebruik dan mock of stub.
Praktische aanbeveling — begin met fake voor elke repository of service. Als fake complexer is dan 50 regels — verdeel het in meerdere klassen. Als fake helemaal niet nodig is (test controleert slechts één scenario met vaste gegevens) — gebruik stub. Als de test controleert of een methode met specifieke parameters is aangeroepen — gebruik mock. Optimaliseer de keuze niet vooraf: schrijf fake, en als het overbodig blijkt, vervang het dan door stub in de specifieke test.
Fake-repository voor Room — typisch voorbeeld van fake op Android. De productie-implementatie van UserRepository gebruikt Room DAO met SQLite-query's. De fake-versie slaat gegevens op in MutableList of HashMap en implementeert dezelfde methoden: getUser(id), saveUser(user), deleteUser(id). Fake bevat zoek-, filter- en sorteerlogica — dezelfde als in de productie-repository, maar zonder SQL. Dit maakt het mogelijk om ViewModel en UseCase te testen zonder configuratie van de Room-database.
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 voor Retrofit API — in plaats van MockWebServer (die stub is, geen fake) kan een implementatie van ApiService worden gemaakt die gegevens uit een in-memory collectie retourneert. Verschil: MockWebServer onderschept HTTP en retourneert JSON, terwijl fake-ApiService op Kotlin-interface-niveau werkt zonder serialisatie. Fake is sneller (geen JSON-parsing) en eenvoudiger te debuggen (werkt in hetzelfde proces, getypeerd). Geschikt voor tests waar HTTP-semantiek (statuscodes, headers) niet belangrijk is.
Fake in Swift — wordt gebouwd via protocollen. De productieklasse implementeert het protocol met echte logica (CoreData, URLSession). De fake-structuur implementeert hetzelfde protocol met in-memory opslag en vereenvoudigde logica. Swift is een taal met waarde-semantiek, dus fake-structuren zijn onveranderlijk en veilig in multi-threaded tests. Dit geeft een voordeel ten opzichte van Android-analogen: toegang tot in-memory gegevens hoeft niet te worden gesynchroniseerd.
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 voor CoreData — in iOS-projecten kan een in-memory NSPersistentContainer worden gemaakt door description.type = NSInMemoryStoreType in te stellen. Dit is een volledige CoreData-stack die in het geheugen werkt. Zo'n fake maakt het mogelijk om NSFetchRequest, predicaten en sorteringen te testen zonder een SQLite-bestand te maken. Snelheid: tests op in-memory CoreData worden 5-10 keer sneller uitgevoerd dan op de schijf-analoog. Nadeel: NSManagedObjectModel moet elke keer worden geconfigureerd.
FakeURLProtocol — een subclass van URLProtocol voor het onderscheppen van netwerkverzoeken op iOS. Wordt geregistreerd via URLProtocol.registerClass(fakeProtocol). Bevat intern een in-memory woordenboek URL -> Data en retourneert gegevens zonder echt verzoek. Verschil met stub: FakeURLProtocol kan de verzoekinhoud, headers controleren en verschillende antwoorden retourneren afhankelijk van de invoergegevens. Dit is fake omdat het routeringslogica voor verzoeken bevat.
Fake als Test Fixture — verplaats fake-klassen naar een gedeelde testmodule (androidTest/sharedTest of TestSupport). Alle tests in het project gebruiken dezelfde InMemoryUserRepository. Dit elimineert het dupliceren van mock-configuratie in elke test en garandeert uniform gedrag. Wijziging van de fake-logica werkt alle tests tegelijk bij. Bij IT Sectr bewaren we fake-klassen in sharedTest/java/com/itSectr/fake/ en koppelen we via implementation project(:sharedTest).
Fake met vooraf ingestelde gegevens — vaak hebben tests een repository nodig die al enkele records bevat. Oplossing: fabrieksmethode fakeWithData(vararg items) of ingebouwde methode addDefaultData(). De fabriek maakt fake, vult deze met typische gegevens en retourneert een gebruiksklaar object. Dit vermindert boilerplate in tests: in plaats van mock-aanroepen te configureren, roept de test eenvoudig FakeUserRepository.withUsers(alice, bob) aan.
Fake met aantallen aanroepen — soms moet niet alleen de status, maar ook het aantal aanroepen worden gecontroleerd. Fake kan tellers bevatten: saveCallCount, getUserCallCount. De test controleert de teller na uitvoering. Dit is een compromis tussen pure fake (statuscontrole) en mock (interactiecontrole). Tellers controleren geen argumenten en aanroepvolgorde — alleen het aantal. Gebruik mock voor argumentcontrole.
Fake met Callback — voor het testen van asynchrone scenario's kan fake bij elke aanroep een callback accepteren: beforeGetUser, afterSaveUser. Dit maakt het mogelijk om vertragingen, fouten te simuleren of tussenliggende statussen te controleren. Deze aanpak is nuttig voor het testen van UI-laadstatussen: fake pauzeert 100 ms, en de test controleert of het scherm een loader toont. In productie is callback afwezig — het is puur testfunctionaliteit.
Veelgestelde vragen
Fake bevat werkende logica — filtert, sorteert, telt. Stub retourneert alleen vooraf bepaalde antwoorden zonder logica. Als een object vertakkingen heeft (if/else, when) — is het fake. Als het alleen return values bevat — is het stub. Fake is duurder in onderhoud, maar geeft realistischere tests.
Wanneer de fake-logica niet overeenkomt met de productielogica. Bijvoorbeeld, FakeUserRepository gebruikt case-sensitive zoeken, terwijl productie case-insensitive is. De test slaagt, maar in werkelijkheid is er een bug. Oplossing: test de fake-logica apart of gebruik fakes alleen voor interfaces met eenvoudige logica (CRUD-bewerkingen). Schrijf voor complexe logica integratietests met een echte database.
In-memory database — een van de varianten van fake. Room.inMemoryDatabaseBuilder() creëert een in-memory SQLite die zich gedraagt als een productiedatabase. Dit is een volwaardige fake. Maar fake kan ook op repository-niveau (zonder SQL) en op netwerkniveau (FakeApiService) zijn. In-memory database is een speciaal geval van fake waarbij de logica maximaal dicht bij de echte ligt.
Ja, maar voorzichtig. Fake voor repository (gegevens), Mock voor AnalyticsTracker (gebeurtenisverificatie). Scheiding per laag: fake voor de gegevenslaag, mock voor de analyse-/loglaag. Maak niet één object tegelijk fake en mock — dit schendt het Single Responsibility Principle en verwart de test.
Test fake met dezelfde tests als de productie-implementatie. Als je UserRepositoryTest hebt die save, get, delete controleert — voer het twee keer uit: met FakeUserRepository en met RealUserRepository. Dit garandeert dat fake het gedrag van de productieklasse herhaalt. Als fake zich anders gaat gedragen — zal de test op beide implementaties falen.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook