Разработка Widget-тестов для Flutter-приложения
Widget-тесты во Flutter занимают ту нишу, которую в мобильной разработке обычно заполняет либо юнит-тест (слишком мелко), либо интеграционный тест (слишком медленно). Рендеринг виджетов, навигация по нажатию кнопки, отображение данных из провайдера — всё это проверяется без запуска на устройстве, за секунды, через 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).
Архитектура виджет-тестов
Структура тест-файла соответствует структуре виджета: 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-тест без мокирования зависимостей — это не виджет-тест, а интеграционный. Для 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, или фиксированный Docker-образ с Ubuntu для мобильных 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-кейсов: пустые состояния, ошибки, loading
- Настройка запуска в CI с отчётом о покрытии
Сроки
3–5 дней для проекта со стандартным набором экранов (10–20 виджетов). Golden Tests на весь UI компонентной библиотеки — отдельная оценка. Стоимость рассчитывается индивидуально.







