Розробка Widget-тестів для Flutter-додатку
Ми часто стикаємося з ситуацією: ви написали віджет, запускаєте тест — і отримуєте No MaterialApp found або RenderFlex overflow. Знайомо? Widget-тести у Flutter займають нішу між юніт-тестами (надто дрібно) та інтеграційними (надто повільно). На відміну від інтеграційних тестів, які ганяють додаток на пристрої, widget-тести рендерять віджети у синтетичному оточенні WidgetTester за мілісекунди — це в 10–20 разів швидше. Вони дозволяють швидко перевірити рендеринг, взаємодії користувача та стани UI без симулятора.
Типові проблеми, з яких починають
Перша і найчастіша помилка — спроба написати 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 або в актуальному Flutter 3.x API: tester.view.physicalSize = const Size(390, 844).
Третя проблема — асинхронність. Widget-тести синхронні за замовчуванням, але часто віджети покладаються на Future, Stream або анімації. Без правильного pump() тест або зависне, або пройде без очікування результату. Розберемо це детально.
Як уникнути RenderFlex overflow у тестах?
Параметр SurfaceSize за замовчуванням — 800×600. Для мобільних екранів це часто призводить до overflow. Наш досвід показує, що краще задавати розмір під конкретний пристрій. Використовуємо setSurfaceSize (додано у Flutter 3) або старий physicalSizeTestValue. Приклад для iPhone 13:
tester.view.physicalSize = const Size(390, 844); Обов'язково скидаємо у tearDown:
tearDown(() { tester.view.resetPhysicalSize(); }); Архітектура віджет-тестів
Структура тест-файлу відповідає структурі віджета: 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('Введите корректный email'), findsOneWidget); }); }); pump() vs pumpAndSettle() — критична відмінність. pump() малює один кадр. pumpAndSettle() крутить кадри доти, доки не залишиться pending-анімацій. На віджетах з нескінченними анімаціями (AnimatedBuilder з repeat: true) pumpAndSettle() зависне назавжди — використовуємо pump(Duration(seconds: 2)).
Як мокувати провайдери в тестах?
Widget-тест без мокування залежностей — це не віджет-тест, а інтеграційний. Ми гарантуємо, що кожен тест ізольований. Для різних state-менеджерів підходи відрізняються:
| State-менеджер | Механізм мокування | Приклад |
|---|---|---|
| Riverpod | ProviderScope.overrides |
userProvider.overrideWithValue(AsyncValue.data(mockUser)) |
| BLoC | BlocProvider з мок-блоком |
BlocProvider<AuthBloc>.value(value: mockAuthBloc) |
| GetIt | Реєстрація мока до тесту | getIt.registerSingleton<AuthService>(MockAuthService()) |
Важливо: у GetIt не забувайте скидати реєстрацію в tearDown — інакше мок перетече в сусідній тест. Для BLoC зручно використовувати mocktail, для Riverpod — mockito або riverpod_generator.
Golden Tests: навіщо і як?
Golden Tests — це візуальні регресійні тести. Віджет рендериться, скріншот порівнюється з еталонним .png у test/goldens/. При першому запуску еталон генерується (flutter test --update-goldens), при наступних — будь-яке піксельне розходження ламає тест. Це дає гарантію, що UI не змінився неочікувано. Golden Tests кращі за стандартні візуальні перевірки, оскільки автоматично виявляють піксельні розходження, недоступні оку.
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 рендерер або Docker-образ.
Пакет golden_toolkit додає loadAppFonts(), що усуває прямокутники замість тексту в еталонах. Наш досвід показує: golden-тести на компонентній бібліотеці (10–20 віджетів) налаштовуються за 2–3 дні.
Async і Future в тестах
Якщо віджет запускає Future при ініціалізації (наприклад, FutureBuilder + HTTP-запит), у тесті потрібно контролювати завершення цього future. Без мока мережевий виклик або впаде, або зависне. Використовуємо mocktail:
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 (гарантується проходження на вашому CI)
- Мокування провайдерів (Riverpod, BLoC, Provider, GetIt)
- Покриття edge-кейсів: порожні стани, помилки, loading
- Налаштування запуску в CI зі звітом про покриття (coverage >= 80% на віджетах)
Строки та вартість
3–5 днів для проекту зі стандартним набором екранів (10–20 віджетів). Golden Tests на весь UI компонентної бібліотеки — окрема оцінка. Вартість розраховується індивідуально — пишіть, оцінимо ваш проект.
Чому варто довірити тести професіоналам?
У нас за плечима десятки Flutter-проектів з комерційним запуском в App Store і Google Play. Ми знаємо, які граблі зустрічаються на шляху: від overflow до golden-розходжень на CI. Ми гарантуємо, що ваші віджет-тести будуть стабільними, швидкими та підтримуваними. Зв'яжіться з нами, щоб обговорити деталі та отримати консультацію.







