Erreurs de Tests Mockito avec Mock Obsolète + Gestion d'Exception Silencieuse
Problème
Quand une PR ajoute de nouvelles méthodes à une classe service (par ex., AnalyticsApiService), et que
ces méthodes sont appelées depuis des chemins de code existants wrappés dans des blocs try/catch, les tests
échouent avec des symptômes trompeurs. L'assertion du test affiche Expected: non-empty, Actual: []
ou Expected: true, Actual: false — mais la cause racine réelle est une MissingStubError
capturée silencieusement.
Contexte / Conditions de Déclenchement
- Symptôme : Le test s'attend à des données (liste non-vide, valeurs spécifiques) mais obtient vide/défaut
- Pas d'erreur évidente : Pas de
MissingStubErrordans la sortie du test car le code de production la capture - Contexte PR : La PR a ajouté de nouvelles méthodes à une classe service annotée
@GenerateMocks - CI affiche aussi : "Generated files are out of date" (
.g.dart/.mocks.dartobsolètes) - Les tests existants passaient avant les modifications de la PR
La Chaîne d'Échec
1. La PR ajoute la méthode getBulkVideoViews() à AnalyticsApiService
2. La PR ajoute _enrichVideosWithBulkStats() à HomeFeedProvider.build()
3. _enrichVideosWithBulkStats appelle getBulkVideoViews (nouvelle méthode)
4. Le mock généré est obsolète — n'a pas du tout getBulkVideoViews
5. Même s'il était régénéré, le setUp du test ne stub pas getBulkVideoViews
6. Le mock lance MissingStubError quand la méthode non-stubbée est appelée
7. Le try/catch du Provider capture TOUTES les exceptions (dont MissingStubError)
8. Le Provider revient à l'état vide → le test voit [] au lieu des données attendues
Solution
Étape 1 : Régénérer les mocks
dart run build_runner build --delete-conflicting-outputs
Cela met à jour tous les fichiers .g.dart et .mocks.dart pour inclure les nouvelles méthodes.
Étape 2 : Identifier les appels de nouvelle méthode dans les chemins de code modifiés
Tracez le chemin du code depuis le point d'entrée du test. Cherchez tout appel de méthode sur les services mockés qui ont été ajoutés ou modifiés par la PR. Faites particulièrement attention aux méthodes appelées après la méthode déjà-stubbée (par ex., les étapes d'enrichissement/post-traitement).
Étape 3 : Ajouter des stubs pour TOUTES les méthodes dans la chaîne d'appel
// Dans le setUp du test :
// Stub existant (était déjà là)
when(mockService.getHomeFeed(
pubkey: anyNamed('pubkey'),
limit: anyNamed('limit'),
)).thenAnswer((_) async => HomeFeedResult(videos: mockVideos));
// NOUVEAUX stubs nécessaires pour les méthodes d'enrichissement ajoutées par la PR
when(mockService.getBulkVideoStats(
argThat(isA<List<String>>()), // paramètre positionnel non-nullable
)).thenAnswer((_) async => <String, BulkVideoStatsEntry>{});
when(mockService.getBulkVideoViews(
argThat(isA<List<String>>()), // paramètre positionnel non-nullable
maxVideos: anyNamed('maxVideos'),
maxConcurrent: anyNamed('maxConcurrent'),
)).thenAnswer((_) async => <String, int>{});
Étape 4 : Gérer les paramètres non-nullable
Le any de Mockito retourne null ce qui échoue pour les paramètres non-nullable. Utilisez :
argThat(isA<List<String>>())pour les paramètres positionnels non-nullableanyNamed('paramName')fonctionne pour les paramètres nommés avec valeurs par défaut (mais PAS pour les paramètres nommés non-nullable sans défaut)
Vérification
Après la correction :
- Exécutez le fichier de test spécifique :
flutter test test/path/to/test.dart - Confirmez que les assertions auparavant échouées passent maintenant
- Exécutez la suite de tests complète pour vérifier l'absence de régressions
Exemple
Avant (échouant) :
Expected: non-empty
Actual: []
test/providers/home_feed_loading_fix_test.dart 253:9
Cause racine : _enrichVideosWithBulkStats a appelé getBulkVideoStats (non-stubbée) →
MissingStubError → capturée par le try/catch du provider → état fallback vide.
Correction : Ajout de stubs dans setUp() pour getBulkVideoStats et getBulkVideoViews.
Après (passant) : Les 11 tests passent, dont les 2 qui échouaient.
Notes
- Régénérez toujours les mocks quand une PR modifie des classes annotées
@GenerateMocks. Les fichiers.mocks.dartdoivent correspondre à l'interface actuelle. - Vérifiez la chaîne d'appel complète, pas juste la méthode primaire. Un stub
getHomeFeedest inutile si le provider appelle ensuiteenrichVideosqui appelle 2 autres méthodes non-stubbées. - Ce pattern est invisible dans la sortie des tests. Le
MissingStubErrorest capturé par la gestion d'erreur de production. Vous ne le verrez jamais sauf si vous ajoutez temporairement desprintdans le bloc catch ou utilisez@GenerateNiceMocks(qui retourne des défauts au lieu de lever, masquant potentiellement d'autres bugs). - Paramètres non-nullable en Dart null-safety : Mockito
anyretournenullau niveau du type. Pour les paramètres positionnels non-nullable, utilisezargThat(isA<Type>())qui retourne un matcher correctement typé.