Fake est une implémentation simplifiée et fonctionnelle d’une dépendance qui se comporte comme un composant réel, mais utilise un stockage en mémoire ou d’autres mécanismes légers au lieu de l’infrastructure de production. Contrairement à un stub, un fake contient de la logique métier réelle — tri, filtrage, agrégation — mais sans effets externes. Une base de données en mémoire au lieu de Room ou un HashMap au lieu de SharedPreferences sont des exemples classiques. Plus de détails dans la classification des test doubles de Martin Fowler.
Points clés
Fake est une implémentation complète mais légère d’une interface, adaptée aux tests. Le terme a été introduit par Gerard Meszaros (2007) dans le livre « xUnit Test Patterns. » Contrairement à un stub qui retourne des réponses prédéfinies, un fake contient du code exécutable : il peut trier une liste, filtrer par condition, compter des enregistrements. La seule différence avec l’implémentation de production est qu’un fake travaille avec des données en mémoire et n’effectue pas d’opérations d’E/S réelles.
Le principal avantage est la vitesse. Les tests avec un fake s’exécutent en millisecondes car il n’y a pas d’accès au disque, au réseau ou à la base de données. Un HashMap en mémoire fonctionne 100 à 1000 fois plus vite que Room ou CoreData. En même temps, un fake teste la logique métier réelle : tri, filtrage, agrégation — tout ce qu’un stub ne peut pas tester car il retourne uniquement ce qu’on lui a dit. Un fake donne la certitude que le code traite correctement les données, au lieu de simplement recevoir une réponse prédéterminée.
Fake est préférable à Stub — si le composant testé effectue plusieurs opérations sur les données (récupération, filtrage, tri, sauvegarde), un stub nécessiterait de configurer chaque appel individuellement. Un fake contient la logique en interne — le test appelle simplement des méthodes et vérifie le résultat. Chez IT Sectr, nous utilisons des fakes pour tous les dépôts dans les tests unitaires : un dépôt fake avec un HashMap couvre 90 % des scénarios sans configurer Mockito ou MockK.
Critère de sélection — déterminez ce que le test vérifie : l’état ou l’interaction. Si le test vérifie l’état (le résultat du travail) et utilise de la logique — utilisez un fake. Si le test a seulement besoin de données d’entrée sans logique — un stub suffit. Si le test vérifie qu’une méthode a été appelée — utilisez un mock. Mélanger les types de test doubles dans un même test complique la compréhension et augmente la fragilité.
| Critère | Fake | Stub | Mock |
|---|---|---|---|
| A de la logique | Oui (simplifiée) | Non | Non |
| Vitesse | Élevée | Maximale | Élevée |
| Vérification de comportement | Indirecte | Non | Oui (verify) |
| Maintenance | Une classe par interface | Configurer par test | Configurer par test |
| Réalisme | Élevé (le code fonctionne) | Faible (données fixes) | Moyen |
| Risque de faux positifs | Faible | Moyen | Élevé (tests fragiles) |
Anti-patron : Fake qui n’est pas un fake — une erreur courante lorsqu’un développeur appelle fake un objet qui est en réalité un stub ou un mock. Si votre InMemoryUserRepository ne contient pas de logique (filtrage, tri) — ce n’est pas un fake, mais un stub avec stockage en mémoire. Un fake se distingue d’un stub précisément par la présence de logique exécutable. Si un dépôt fake retourne simplement ce qu’on y a mis et ne traite pas les données — utilisez un mock ou un stub.
Recommandation pratique — commencez par un fake pour chaque dépôt ou service. Si un fake dépasse 50 lignes — divisez-le en plusieurs classes. Si un fake n’est pas du tout nécessaire (le test ne vérifie qu’un seul scénario avec des données fixes) — utilisez un stub. Si le test vérifie qu’une méthode a été appelée avec des paramètres spécifiques — utilisez un mock. N’optimisez pas le choix à l’avance : écrivez un fake, et s’il s’avère excessif, remplacez-le par un stub dans un test spécifique.
Dépôt fake pour Room — un exemple typique de fake sur Android. L’implémentation de production de UserRepository utilise Room DAO avec des requêtes SQLite. La version fake stocke les données dans une MutableList ou un HashMap et implémente les mêmes méthodes : getUser(id), saveUser(user), deleteUser(id). Le fake contient de la logique de recherche, filtrage et tri — identique au dépôt de production, mais sans SQL. Cela permet de tester ViewModel et UseCase sans configurer une base de données 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 pour API Retrofit — au lieu de MockWebServer (qui est un stub, pas un fake), vous pouvez créer une implémentation d’ApiService qui retourne des données depuis une collection en mémoire. La différence : MockWebServer intercepte HTTP et retourne du JSON, tandis qu’un fake ApiService fonctionne au niveau de l’interface Kotlin sans sérialisation. Un fake est plus rapide (pas d’analyse JSON) et plus facile à déboguer (s’exécute dans le même processus, typé). Adapté aux tests où la sémantique HTTP (codes d’état, en-têtes) n’est pas importante.
Fake en Swift — se construit via des protocoles. La classe de production implémente le protocole avec de la logique réelle (CoreData, URLSession). La structure fake implémente le même protocole avec un stockage en mémoire et une logique simplifiée. Swift est un langage à sémantique de valeur, donc les structures fake sont immuables et sûres dans les tests multithreads. Cela donne un avantage sur les équivalents Android : pas besoin de synchroniser l’accès aux données en mémoire.
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 pour CoreData — dans les projets iOS, vous pouvez créer un NSPersistentContainer en mémoire en définissant description.type = NSInMemoryStoreType. C’est une pile CoreData complète, mais fonctionnant en mémoire. Un tel fake permet de tester NSFetchRequest, les prédicats et les tris sans créer de fichier SQLite. Vitesse : les tests sur CoreData en mémoire s’exécutent 5 à 10 fois plus vite que sur l’équivalent disque. Inconvénient : il faut configurer NSManagedObjectModel à chaque fois.
FakeURLProtocol — une sous-classe d’URLProtocol pour intercepter les requêtes réseau sur iOS. Elle est enregistrée via URLProtocol.registerClass(fakeProtocol). En interne, elle contient un dictionnaire URL -> Data en mémoire et retourne les données sans requête réelle. La différence avec un stub : FakeURLProtocol peut vérifier le corps de la requête, les en-têtes et retourner différentes réponses selon les données d’entrée. C’est un fake car il contient de la logique de routage de requêtes.
Fake comme Test Fixture — placez les classes fake dans un module de test partagé (androidTest/sharedTest ou TestSupport). Tous les tests du projet utilisent le même InMemoryUserRepository. Cela élimine la duplication de configuration des objets mock dans chaque test et garantit un comportement uniforme. Modifier la logique du fake met à jour tous les tests simultanément. Chez IT Sectr, nous stockons les classes fake dans sharedTest/java/com/itSectr/fake/ et les incluons via implementation project(:sharedTest).
Fake avec données prédéfinies — les tests ont souvent besoin d’un dépôt contenant déjà des enregistrements. Solution : une méthode d’usine fakeWithData(vararg items) ou une méthode intégrée addDefaultData(). L’usine crée un fake, le remplit de données typiques et retourne un objet prêt à l’emploi. Cela réduit le code passe-partout dans les tests : au lieu de configurer des appels mock, le test appelle simplement FakeUserRepository.withUsers(alice, bob).
Fake avec comptage d’appels — parfois il est nécessaire de vérifier non seulement l’état mais aussi le nombre d’invocations. Un fake peut contenir des compteurs : saveCallCount, getUserCallCount. Le test vérifie le compteur après l’exécution. C’est un compromis entre un fake pur (vérification d’état) et un mock (vérification d’interaction). Les compteurs ne vérifient pas les arguments ni l’ordre des appels — seulement le nombre. Pour la vérification des arguments, utilisez un mock.
Fake avec Callback — pour tester des scénarios asynchrones, un fake peut accepter un callback à chaque appel : beforeGetUser, afterSaveUser. Cela permet de simuler des retards, des erreurs ou de vérifier des états intermédiaires. Cette approche est utile pour tester les états de chargement de l’interface : le fake marque une pause de 100 ms et le test vérifie que l’écran affiche un chargeur. Le callback est absent en production — c’est une fonctionnalité purement de test.
Questions fréquentes
Fake contient de la logique fonctionnelle — filtre, trie, compte. Stub retourne uniquement des réponses prédéterminées sans logique. Si un objet a des branchements (if/else, when) — c’est un fake. S’il ne contient que des valeurs de retour — c’est un stub. Un fake est plus coûteux à maintenir mais fournit des tests plus réalistes.
Quand la logique du fake ne correspond pas à la logique de production. Par exemple, FakeUserRepository utilise une recherche sensible à la casse, tandis que la version de production est insensible à la casse. Le test réussit, mais en réalité il y a un bug. Solution : testez la logique du fake séparément ou utilisez des fakes uniquement pour les interfaces à logique simple (opérations CRUD). Pour une logique complexe, écrivez des tests d’intégration avec une base de données réelle.
Base de données en mémoire est un type de fake. Room.inMemoryDatabaseBuilder() crée du SQLite en mémoire qui se comporte comme une base de données de production. C’est un fake à part entière. Mais un fake peut aussi exister au niveau du dépôt (sans SQL) et au niveau réseau (FakeApiService). Une base de données en mémoire est un cas particulier de fake où la logique est aussi proche que possible de la réalité.
Oui, mais avec précaution. Fake pour le dépôt (données), Mock pour AnalyticsTracker (vérification d’événements). Séparation par couches : fake pour la couche de données, mock pour la couche d’analyse/de journalisation. Ne faites pas d’un même objet à la fois un fake et un mock — cela viole le Principe de Responsabilité Unique et perturbe le test.
Testez le fake avec les mêmes tests que l’implémentation de production. Si vous avez un UserRepositoryTest qui vérifie save, get, delete — exécutez-le deux fois : avec FakeUserRepository et avec RealUserRepository. Cela garantit que le fake reproduit le comportement de la classe de production. Si le fake commence à se comporter différemment — le test échouera sur les deux implémentations.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi