mockito-stale-mock-silent-trycatch-failure

Par divinevideo · divine-mobile

Corrige les échecs de tests Flutter/Dart trompeurs où les données attendues sont vides ([]) ou correspondent à des valeurs par défaut au lieu de la réponse mockée, causés par des mocks Mockito générés obsolètes ou des stubs manquants silencieusement avalés par des blocs try/catch dans le code provider/repository. À utiliser quand : (1) Le test attend des données non vides mais obtient [], (2) Les stubs mock pour une méthode sont configurés mais le code sous test retourne des résultats de repli/vides, (3) Une PR a ajouté de nouvelles méthodes à une classe de service et des tests qui passaient auparavant échouent maintenant avec des assertions trompeuses "expected X, actual: empty", (4) Erreur CI "Generated files are out of date" accompagnée d'échecs de tests. La cause racine est un `MissingStubError` lancé par des méthodes mock sans stub, intercepté par des blocs try/catch de production, provoquant un repli silencieux vers un état vide.

npx skills add https://github.com/divinevideo/divine-mobile --skill mockito-stale-mock-silent-trycatch-failure

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 MissingStubError dans 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.dart obsolè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-nullable
  • anyNamed('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 :

  1. Exécutez le fichier de test spécifique : flutter test test/path/to/test.dart
  2. Confirmez que les assertions auparavant échouées passent maintenant
  3. 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.dart doivent correspondre à l'interface actuelle.
  • Vérifiez la chaîne d'appel complète, pas juste la méthode primaire. Un stub getHomeFeed est inutile si le provider appelle ensuite enrichVideos qui appelle 2 autres méthodes non-stubbées.
  • Ce pattern est invisible dans la sortie des tests. Le MissingStubError est capturé par la gestion d'erreur de production. Vous ne le verrez jamais sauf si vous ajoutez temporairement des print dans 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 any retourne null au niveau du type. Pour les paramètres positionnels non-nullable, utilisez argThat(isA<Type>()) qui retourne un matcher correctement typé.

Références

Skills similaires