Fake è un’implementazione semplificata e funzionante di una dipendenza che si comporta come un componente reale, ma utilizza memoria interna o altri meccanismi leggeri invece dell’infrastruttura di produzione. A differenza di uno stub, un fake contiene logica di business reale — ordinamento, filtraggio, aggregazione — ma senza effetti esterni. Un database in memoria invece di Room o una HashMap invece di SharedPreferences sono esempi classici. Maggiori dettagli in classificazione dei test doubles di Martin Fowler.
Punti chiave
Fake è un’implementazione completa ma leggera di un’interfaccia, adatta ai test. Il termine è stato introdotto da Gerard Meszaros (2007) nel libro “xUnit Test Patterns.” A differenza di uno stub, che restituisce risposte predefinite, un fake contiene codice eseguibile: può ordinare una lista, filtrare per condizione, contare record. L’unica differenza dall’implementazione di produzione è che un fake lavora con dati in memoria e non esegue operazioni di I/O reali.
Il vantaggio principale è la velocità. I test con un fake vengono eseguiti in millisecondi perché non c’è accesso a disco, rete o database. Una HashMap in memoria funziona 100–1000 volte più veloce di Room o CoreData. Allo stesso tempo, un fake testa la logica di business reale: ordinamento, filtraggio, aggregazione — tutto ciò che uno stub non può testare perché restituisce solo ciò che gli è stato detto. Un fake dà la certezza che il codice elabora correttamente i dati, invece di ricevere semplicemente una risposta predeterminata.
Fake è preferibile a Stub — se il componente sotto test esegue più operazioni sui dati (recupero, filtraggio, ordinamento, salvataggio), uno stub richiederebbe di configurare ogni chiamata individualmente. Un fake contiene la logica al suo interno — il test chiama semplicemente i metodi e verifica il risultato. In IT Sectr, usiamo fakes per tutti i repository nei test unitari: un repository fake con una HashMap copre il 90% degli scenari senza configurare Mockito o MockK.
Criterio di selezione — determina cosa verifica il test: stato o interazione. Se il test verifica lo stato (il risultato del lavoro) e usa logica — usa un fake. Se il test ha bisogno solo di dati di input senza logica — uno stub è sufficiente. Se il test verifica che un metodo è stato chiamato — usa un mock. Mescolare tipi di test doubles in un test complica la comprensione e aumenta la fragilità.
| Criterio | Fake | Stub | Mock |
|---|---|---|---|
| Ha logica | Sì (semplificata) | No | No |
| Velocità | Alta | Massima | Alta |
| Verifica del comportamento | Indiretta | No | Sì (verify) |
| Manutenzione | Una classe per interfaccia | Configurare per test | Configurare per test |
| Realismo | Alto (il codice funziona) | Basso (dati fissi) | Medio |
| Rischio falsi positivi | Basso | Medio | Alto (test fragili) |
Anti-pattern: Fake che non è un fake — un errore comune quando uno sviluppatore chiama fake un oggetto che in realtà è uno stub o un mock. Se il tuo InMemoryUserRepository non contiene logica (filtraggio, ordinamento) — non è un fake, ma uno stub con memoria interna. Un fake si differenzia da uno stub proprio per la presenza di logica eseguibile. Se un repository fake restituisce semplicemente ciò che vi è stato inserito e non elabora i dati — usa un mock o uno stub.
Raccomandazione pratica — inizia con un fake per ogni repository o servizio. Se un fake supera 50 righe — dividilo in più classi. Se un fake non è affatto necessario (il test verifica un solo scenario con dati fissi) — usa uno stub. Se il test verifica che un metodo è stato chiamato con parametri specifici — usa un mock. Non ottimizzare la scelta in anticipo: scrivi un fake e, se risulta eccessivo, sostituiscilo con uno stub in un test specifico.
Repository fake per Room — un tipico esempio di fake su Android. L’implementazione di produzione di UserRepository usa Room DAO con query SQLite. La versione fake memorizza i dati in una MutableList o HashMap e implementa gli stessi metodi: getUser(id), saveUser(user), deleteUser(id). Il fake contiene logica di ricerca, filtraggio e ordinamento — identica al repository di produzione, ma senza SQL. Ciò consente di testare ViewModel e UseCase senza configurare un database 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 per API Retrofit — invece di MockWebServer (che è uno stub, non un fake), puoi creare un’implementazione di ApiService che restituisce dati da una collezione in memoria. La differenza: MockWebServer intercetta HTTP e restituisce JSON, mentre un fake ApiService funziona a livello di interfaccia Kotlin senza serializzazione. Un fake è più veloce (nessun parsing JSON) e più facile da debuggare (gira nello stesso processo, tipizzato). Adatto per test dove la semantica HTTP (codici di stato, header) non è importante.
Fake in Swift — si costruisce tramite protocolli. La classe di produzione implementa il protocollo con logica reale (CoreData, URLSession). La struttura fake implementa lo stesso protocollo con memoria interna e logica semplificata. Swift è un linguaggio con semantica di valore, quindi le strutture fake sono immutabili e sicure nei test multithread. Questo dà un vantaggio rispetto agli analoghi Android: non è necessario sincronizzare l’accesso ai dati in memoria.
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 per CoreData — nei progetti iOS, puoi creare un NSPersistentContainer in memoria impostando description.type = NSInMemoryStoreType. Questo è uno stack CoreData completo, ma funzionante in memoria. Tale fake consente di testare NSFetchRequest, predicati e ordinamenti senza creare un file SQLite. Velocità: i test su CoreData in memoria vengono eseguiti 5–10 volte più velocemente rispetto all’analogo su disco. Svantaggio: è necessario configurare NSManagedObjectModel ogni volta.
FakeURLProtocol — una sottoclasse di URLProtocol per intercettare richieste di rete su iOS. Viene registrata tramite URLProtocol.registerClass(fakeProtocol). Internamente contiene un dizionario URL -> Data in memoria e restituisce dati senza una richiesta reale. La differenza da uno stub: FakeURLProtocol può verificare il corpo della richiesta, gli header e restituire risposte diverse in base ai dati di input. Questo è un fake perché contiene logica di routing delle richieste.
Fake come Test Fixture — metti le classi fake in un modulo di test condiviso (androidTest/sharedTest o TestSupport). Tutti i test del progetto usano lo stesso InMemoryUserRepository. Ciò elimina la duplicazione della configurazione di oggetti mock in ogni test e garantisce un comportamento uniforme. Modificare la logica del fake aggiorna tutti i test simultaneamente. In IT Sectr, memorizziamo le classi fake in sharedTest/java/com/itSectr/fake/ e le includiamo tramite implementation project(:sharedTest).
Fake con dati preimpostati — spesso i test necessitano di un repository che già contenga alcuni record. Soluzione: un metodo factory fakeWithData(vararg items) o un metodo integrato addDefaultData(). La factory crea un fake, lo riempie con dati tipici e restituisce un oggetto pronto all’uso. Ciò riduce il boilerplate nei test: invece di configurare chiamate mock, il test chiama semplicemente FakeUserRepository.withUsers(alice, bob).
Fake con conteggio delle chiamate — a volte è necessario verificare non solo lo stato ma anche il numero di invocazioni. Un fake può contenere contatori: saveCallCount, getUserCallCount. Il test verifica il contatore dopo l’esecuzione. Questo è un compromesso tra un fake puro (verifica dello stato) e un mock (verifica dell’interazione). I contatori non verificano argomenti o ordine delle chiamate — solo il numero. Per la verifica degli argomenti, usa un mock.
Fake con Callback — per testare scenari asincroni, un fake può accettare un callback a ogni chiamata: beforeGetUser, afterSaveUser. Ciò consente di simulare ritardi, errori o verificare stati intermedi. Questo approccio è utile per testare gli stati di caricamento dell’interfaccia: il fake si mette in pausa per 100 ms e il test verifica che lo schermo mostri un indicatore di caricamento. Il callback è assente in produzione — è funzionalità puramente di test.
Domande frequenti
Fake contiene logica funzionale — filtra, ordina, conta. Stub restituisce solo risposte predeterminate senza logica. Se un oggetto ha ramificazioni (if/else, when) — è un fake. Se contiene solo valori di ritorno — è uno stub. Un fake è più costoso da mantenere ma fornisce test più realistici.
Quando la logica del fake non corrisponde alla logica di produzione. Ad esempio, FakeUserRepository usa una ricerca case-sensitive, mentre la versione di produzione è case-insensitive. Il test passa, ma in realtà c’è un bug. Soluzione: testa la logica del fake separatamente o usa fakes solo per interfacce con logica semplice (operazioni CRUD). Per logica complessa, scrivi test di integrazione con un database reale.
Database in memoria è un tipo di fake. Room.inMemoryDatabaseBuilder() crea SQLite in memoria che si comporta come un database di produzione. Questo è un fake a tutti gli effetti. Ma un fake può esistere anche a livello di repository (senza SQL) e a livello di rete (FakeApiService). Un database in memoria è un caso speciale di fake in cui la logica è il più vicino possibile a quella reale.
Sì, ma con cautela. Fake per il repository (dati), Mock per AnalyticsTracker (verifica eventi). Separazione per livelli: fake per il livello dati, mock per il livello di analisi/log. Non fare di un oggetto contemporaneamente un fake e un mock — viola il Principio di Responsabilità Unica e confonde il test.
Testa il fake con gli stessi test dell’implementazione di produzione. Se hai un UserRepositoryTest che verifica save, get, delete — eseguilo due volte: con FakeUserRepository e con RealUserRepository. Ciò garantisce che il fake replichi il comportamento della classe di produzione. Se il fake inizia a comportarsi diversamente — il test fallirà su entrambe le implementazioni.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche