Rédiger des tests de widgets Flutter
Table des matières
- Configuration et paramétrage
- Composants principaux
- Flux de travail : Implémenter un test de widget
- Interaction et gestion d'état
- Exemples
Configuration et paramétrage
Assurez-vous que l'environnement de test est correctement configuré avant de rédiger des tests de widgets.
- Ajoutez la dépendance
flutter_testà la sectiondev_dependenciesdu fichierpubspec.yaml. - Placez tous les fichiers de test dans le répertoire
test/à la racine du projet. - Suffixez tous les noms de fichiers de test par
_test.dart(par exemple,widget_test.dart).
Composants principaux
Utilisez les composants flutter_test suivants pour interagir avec et valider l'arborescence des widgets :
WidgetTester: L'interface principale pour construire et interagir avec les widgets dans l'environnement de test. Fournie automatiquement par la fonctiontestWidgets().Finder: Localise les widgets dans l'environnement de test (par exemple,find.text('Submit'),find.byType(TextField),find.byKey(Key('submit_btn'))).Matcher: Vérifie la présence ou l'état des widgets localisés par unFinder(par exemple,findsOneWidget,findsNothing,findsNWidgets(2),matchesGoldenFile).
Flux de travail : Implémenter un test de widget
Copiez la liste de contrôle suivante pour suivre la progression lors de l'implémentation d'un nouveau test de widget.
Progression des tâches
- [ ] Étape 1 : Définir le test. Utilisez
testWidgets('description', (WidgetTester tester) async { ... }). - [ ] Étape 2 : Construire le widget. Appelez
await tester.pumpWidget(MyWidget())pour afficher l'interface utilisateur. Enveloppez le widget dans un widgetMaterialAppouDirectionalitys'il nécessite des données directionnelles ou thématiques héritées. - [ ] Étape 3 : Localiser les éléments. Instanciez les objets
Finderpour les widgets cibles. - [ ] Étape 4 : Vérifier l'état initial. Utilisez
expect(finder, matcher)pour valider le rendu initial. - [ ] Étape 5 : Simuler les interactions. Exécutez des gestes ou des entrées (par exemple,
await tester.tap(buttonFinder)). - [ ] Étape 6 : Reconstruire l'arborescence. Appelez
await tester.pump()ouawait tester.pumpAndSettle()pour traiter les changements d'état. - [ ] Étape 7 : Vérifier l'état mis à jour. Utilisez
expect()pour valider l'interface utilisateur après l'interaction. - [ ] Étape 8 : Exécuter et valider. Exécutez
flutter test test/your_test_file_test.dart. - [ ] Étape 9 : Boucle de rétroaction. Examinez la sortie du test -> identifiez les matchers défaillants -> ajustez la logique du widget ou les assertions de test -> relancez jusqu'à réussite.
Interaction et gestion d'état
Appliquez la logique conditionnelle suivante en fonction du type d'interaction ou de changement d'état en cours de test :
- Si vous testez un rendu statique : Appelez
await tester.pumpWidget()une seule fois, puis exécutez immédiatement les assertionsexpect(). - Si vous testez des changements d'état standard (par exemple, des appuis de bouton) :
- Appelez
await tester.tap(finder). - Appelez
await tester.pump()pour déclencher une reconstruction d'une seule image.
- Appelez
- Si vous testez des animations, des transitions ou des mises à jour d'interface utilisateur asynchrones :
- Déclenchez l'action (par exemple,
await tester.drag(finder, Offset(500, 0))). - Appelez
await tester.pumpAndSettle()pour pomper les images à plusieurs reprises jusqu'à ce qu'aucune autre image ne soit programmée (fin de l'animation).
- Déclenchez l'action (par exemple,
- Si vous testez l'entrée de texte : Appelez
await tester.enterText(textFieldFinder, 'Input string'). - Si vous testez des éléments dans une liste dynamique ou longue : Appelez
await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder)pour vous assurer que le widget cible est rendu avant d'interagir avec lui.
Exemples
Implémentation de test de widget haute fidélité
Widget cible (lib/todo_list.dart) :
import 'package:flutter/material.dart';
class TodoList extends StatefulWidget {
const TodoList({super.key});
@override
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
final todos = <String>[];
final controller = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: [
TextField(controller: controller),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (_) => setState(() => todos.removeAt(index)),
child: ListTile(title: Text(todo)),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todos.add(controller.text);
controller.clear();
});
},
child: const Icon(Icons.add),
),
),
);
}
}
Implémentation du test (test/todo_list_test.dart) :
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/todo_list.dart';
void main() {
testWidgets('Add and remove a todo item', (WidgetTester tester) async {
// 1. Build the widget
await tester.pumpWidget(const TodoList());
// 2. Verify initial state
expect(find.byType(ListTile), findsNothing);
// 3. Enter text into the TextField
await tester.enterText(find.byType(TextField), 'Buy groceries');
// 4. Tap the add button
await tester.tap(find.byType(FloatingActionButton));
// 5. Rebuild the widget to reflect the new state
await tester.pump();
// 6. Verify the item was added
expect(find.text('Buy groceries'), findsOneWidget);
// 7. Swipe the item to dismiss it
await tester.drag(find.byType(Dismissible), const Offset(500, 0));
// 8. Build the widget until the dismiss animation ends
await tester.pumpAndSettle();
// 9. Verify the item was removed
expect(find.text('Buy groceries'), findsNothing);
});
}