Розробка Widget-тестів для Flutter-додатків
Widget-тести у Flutter займають нішу, яку зазвичай заповнюють або unit-тести (занадто дрібні), або інтеграційні тести (занадто повільні) у мобільній розробці. Рендеринг віджетів, навігація за дотиком кнопки, відображення даних з провайдера — все це перевіряється без запуску на фізичному пристрої, за секунди, через WidgetTester та синтетичний рушій рендерингу.
Поширені початкові проблеми
Найчастіша помилка: спроба написати widget-тест для віджета, який залежить від реального BuildContext — наприклад, такий, що читає Theme.of(context) і падає з No MaterialApp found або No Directionality widget. Рішення — завжди обертати тестований віджет у MaterialApp або мінімальний Directionality:
await tester.pumpWidget(
MaterialApp(home: MyWidget()),
);
Друга проблема — RenderFlex overflowed у тестах, яких не було в дебагері. Це означає, що розмір WidgetTester за замовчуванням (800×600) не збігається з реальним пристроєм. Виправте це через tester.binding.window.physicalSizeTestValue або tester.view.physicalSize = const Size(390, 844) (у поточному Flutter 3.x API).
Архітектура Widget-тестів
Структура тест-файлу відображає структуру віджета: test/widgets/ дзеркалює lib/widgets/. Кожен тест-файл відповідає одному віджету або одному екрану.
group('LoginScreen', () {
testWidgets('shows error when email is invalid', (tester) async {
await tester.pumpWidget(MaterialApp(home: LoginScreen()));
await tester.enterText(find.byKey(Key('email_field')), 'not-an-email');
await tester.tap(find.byKey(Key('submit_button')));
await tester.pump(); // синхронний кадр
expect(find.text('Введіть коректну електронну пошту'), findsOneWidget);
});
});
pump() vs pumpAndSettle() — критичне розрізнення. pump() малює один кадр. pumpAndSettle() циклює кадри, поки не залишиться жодних очікуючих анімацій. На віджетах з нескінченними анімаціями (AnimatedBuilder з repeat: true) pumpAndSettle() зависне навічно — використовуйте pump(Duration(seconds: 2)).
Мокування провайдерів та залежностей
Widget-тест без мокування залежностей — це не widget-тест, це інтеграційний тест. Для Riverpod переопишіть провайдери через ProviderScope.overrides:
await tester.pumpWidget(
ProviderScope(
overrides: [
userProvider.overrideWithValue(AsyncValue.data(mockUser)),
],
child: MaterialApp(home: ProfileScreen()),
),
);
Для BLoC — BlocProvider з мок-блоком через mocktail або mockito. Для GetIt — зареєструйте мок-реалізацію перед тестом і скиньте після через tearDown.
Golden Tests
Golden Tests — окремена категорія. Віджет рендериться, скриншот порівнюється з еталонним .png у test/goldens/. При першому запуску еталон генерується (flutter test --update-goldens), при наступних — будь-яке пікельне розходження ламає тест.
testWidgets('PrimaryButton golden', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(child: PrimaryButton(label: 'Зберегти')),
),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile('goldens/primary_button.png'),
);
});
Проблема з Golden Tests: вони залежать від платформи. Шрифти, згладжування, рендеринг тіней — усе відрізняється на macOS, Linux та Windows CI. Рішення — запускайте golden-тести тільки на певній платформі (canvaskit рушій у CI через flutter test --platform chrome для web, або фіксований Ubuntu Docker-образ для мобільних golden'ів).
Пакет golden_toolkit (pub.dev) додає loadAppFonts(), що усуває прямокутники замість тексту в еталонах.
Async та Future у тестах
Якщо віджет запускає Future при ініціалізації (наприклад, FutureBuilder + HTTP-запит), тест повинен контролювати завершення цього future. Без мокування мережевий виклик либо падає, либо зависає.
when(() => mockApiService.getUser()).thenAnswer((_) async => mockUser);
await tester.pumpWidget(/* ... */);
await tester.pump(); // запускає FutureBuilder
await tester.pump(Duration.zero); // чекаємо завершення Future
Fake замість Mock — коли поведінка складна. Реалізуйте FakeAuthService extends AuthService, переопишіть необхідні методи — чище, ніж stub кожного виклику.
Що включено
- Написання widget-тестів для всіх ключових екранів та компонентів
- Налаштування Golden Tests з коректною платформою для CI
- Мокування провайдерів (Riverpod, BLoC, Provider, GetIt)
- Охоплення edge-케йсів: порожні стани, помилки, завантаження
- Налаштування запуску у CI з звітом про покриття
Строки
3–5 днів для проекту зі стандартним набором екранів (10–20 віджетів). Golden Tests для всієї бібліотеки UI-компонентів — окремо розрахунок. Вартість розраховується індивідуально.







