Flutter Widget Testing Cheatsheet
(c) Написано с использованием нейросетей ChatGPT 5
Минимальный набор вещей, которые стоит знать при написании виджет-тестов.
1. Основные пакеты
dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
2. Запуск теста
dart
testWidgets('description', (WidgetTester tester) async {
await tester.pumpWidget(MyWidget());
});
tester.pumpWidget()
— строит дерево виджетов.tester.pump()
— перерисовка дерева (один кадр).tester.pumpAndSettle()
— ждёт окончания всех анимаций и микротасков.
3. Поиск виджетов
find.byType(MyWidget)
— ищет виджет по типу.find.text('Hello')
— ищет текст.find.byKey(ValueKey('myKey'))
— ищет виджет по ключу.find.byWidgetPredicate((w) => w is MyWidget)
— гибкий поиск по условию.- Важно:
find.byWidget(widgetInstance)
ищет точно этот объект, почти не используют.
4. Взаимодействие с виджетами
await tester.tap(finder)
— тап по виджету.await tester.enterText(finder, 'text')
— ввод текста.await tester.drag(finder, Offset(dx, dy))
— свайп/драг.- Добавлять
await tester.pump()
после действий, чтобы обновить дерево.
⚡ Для ожидаемого игнорирования событий:
dart
await tester.tap(finder, warnIfMissed: false);
5. Проверки
dart
expect(find.text('Hello'), findsOneWidget);
expect(find.byType(MyWidget), findsNothing);
expect(find.byKey(ValueKey('myKey')), findsWidgets); // несколько
findsOneWidget
,findsNothing
,findsWidgets
— основные матчеры.
6. Ключи (Key)
- Очень полезны для поиска конкретного виджета среди нескольких одинаковых.
- Использовать
ValueKey
,UniqueKey
или кастомные ключи. - Пример:
dart
FFTIconButton.standard(
key: ValueKey('close-button'),
...
)
7. Работа с состоянием
- Если виджет использует
setState
илиProvider/BLoC
, нужно вызватьpump()
после изменения состояния:
dart
myState.toggleFlag();
await tester.pump();
- Для анимаций:
dart
await tester.pumpAndSettle(); // ждём завершения всех анимаций
8. Приватные фабричные подклассы
Если виджет создаётся через factory + приватный подкласс:
dart
sealed class A extends StatelessWidget { ... }
class _A extends A { ... }
find.byType(A)
не сработает, потому что реально создаётся_A
.- Решения:
dart
find.byWidgetPredicate((w) => w is A)
find.byKey(ValueKey('some-key'))
9. Игнорирование и проверка взаимодействий
IgnorePointer
/AbsorbPointer
блокируют события:
dart
final ignore = tester.widget<IgnorePointer>(
find.ancestor(of: finder, matching: find.byType(IgnorePointer))
);
expect(ignore.ignoring, true);
- Можно безопасно тапать с
warnIfMissed: false
в таких случаях.
10. Ассеты в тестах
- Если виджет грузит ассеты (
rootBundle.loadString
):- Положить тестовые ассеты в
test/assets/...
. - Добавить их в
pubspec.yaml
. - Загружать через:
- Положить тестовые ассеты в
dart
final jsonStr = await rootBundle.loadString('test/assets/test_tokens.json');
tokens = FFTokens.fromJson(jsonDecode(jsonStr));
- Альтернатива: мокнуть токены или использовать дефолтные значения (
FFTokens.fallback()
).
11. Полезные советы
- Комбинируй
find.byType
+byKey
для надёжного поиска. - Всегда
pump()
после изменения состояния. - Для анимаций и появления/скрытия —
pumpAndSettle()
. - Добавляй ключи на важные элементы (кнопки, текст, контейнеры).
- Используй
WidgetPredicate
, если работаешь с приватными фабричными подклассами. - Для ожидаемых неудачных тапов —
warnIfMissed: false
.