Developing Widget Tests for Flutter Apps
We often encounter this situation: you write a widget, run the test — and get No MaterialApp found or RenderFlex overflow. Sound familiar? Widget tests in Flutter fill the gap between unit tests (too small) and integration tests (too slow). Unlike integration tests that run the app on a device, widget tests render widgets in the synthetic WidgetTester environment in milliseconds — 10–20 times faster. They allow quick verification of rendering, user interactions, and UI states without a simulator.
Typical Problems to Start With
The first and most common mistake is trying to write a widget test for a widget that depends on a real BuildContext. For example, a widget uses Theme.of(context) and crashes with No MaterialApp found or No Directionality widget. The solution — always wrap the tested widget in MaterialApp or a minimal Directionality:
await tester.pumpWidget(
MaterialApp(home: MyWidget()),
);
The second common problem — RenderFlex overflowed in tests that didn't appear in the debugger. This happens because the default WidgetTester size (800×600) doesn't match the real device. Fix via tester.binding.window.physicalSizeTestValue or in current Flutter 3.x API: tester.view.physicalSize = const Size(390, 844).
The third problem — asynchrony. Widget tests are synchronous by default, but widgets often rely on Futures, Streams, or animations. Without proper pump() the test either hangs or passes without waiting for the result. Let's dive into this.
How to Avoid RenderFlex Overflow in Tests?
The default SurfaceSize is 800×600. For mobile screens this often leads to overflow. Our experience shows it's better to set the size to a specific device. Use setSurfaceSize (added in Flutter 3) or the older physicalSizeTestValue. Example for iPhone 13:
tester.view.physicalSize = const Size(390, 844);
Always reset in tearDown:
tearDown(() {
tester.view.resetPhysicalSize();
});
Architecture of Widget Tests
The test file structure mirrors the widget structure: test/widgets/ mirrors lib/widgets/. Each test file covers one widget or one screen. This simplifies navigation and maintenance.
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(); // synchronous frame
expect(find.text('Enter a valid email'), findsOneWidget);
});
});
pump() vs pumpAndSettle() — critical difference. pump() draws one frame. pumpAndSettle() keeps drawing frames until no pending animations remain. On widgets with infinite animations (AnimatedBuilder with repeat: true) pumpAndSettle() hangs forever — use pump(Duration(seconds: 2)) instead.
How to Mock Providers in Tests?
A widget test without mocking dependencies is not a widget test, it's an integration test. We ensure every test is isolated. Approaches differ by state manager:
| State Manager | Mocking Mechanism | Example |
|---|---|---|
| Riverpod | ProviderScope.overrides |
userProvider.overrideWithValue(AsyncValue.data(mockUser)) |
| BLoC | BlocProvider with mock bloc |
BlocProvider<AuthBloc>.value(value: mockAuthBloc) |
| GetIt | Register mock before test | getIt.registerSingleton<AuthService>(MockAuthService()) |
Important: in GetIt, always unregister in tearDown — otherwise the mock leaks into the next test. For BLoC, mocktail is convenient; for Riverpod, mockito or riverpod_generator.
Golden Tests: Why and How?
Golden Tests are visual regression tests. The widget is rendered, and a screenshot is compared with a reference .png in test/goldens/. On first run, the reference is generated (flutter test --update-goldens); on subsequent runs, any pixel difference breaks the test. This guarantees that the UI hasn't changed unexpectedly. Golden Tests are better than standard visual checks because they automatically detect pixel differences invisible to the eye.
testWidgets('PrimaryButton golden', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(child: PrimaryButton(label: 'Save')),
),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile('goldens/primary_button.png'),
);
});
However, Golden Tests are platform-dependent. Fonts, anti-aliasing, shadow rendering — all differ on macOS, Linux, and Windows CI. Solution: run golden tests on a single platform only, using the canvaskit renderer or a Docker image.
The golden_toolkit package adds loadAppFonts(), eliminating rectangles instead of text in references. Our experience shows that golden tests on a component library (10–20 widgets) are set up in 2–3 days.
Async and Future in Tests
If a widget launches a Future on initialization (e.g., FutureBuilder + HTTP request), the test needs to control that future's completion. Without mocking, the network call either fails or hangs. Use mocktail:
when(() => mockApiService.getUser()).thenAnswer((_) async => mockUser);
await tester.pumpWidget(/* ... */);
await tester.pump(); // triggers FutureBuilder
await tester.pump(Duration.zero); // waits for Future to complete
Fake instead of Mock — when behavior is complex. Implement FakeAuthService extends AuthService, override needed methods — cleaner than stubbing every call.
What's Included in the Work
- Writing widget tests for all key screens and components
- Setting up Golden Tests with the correct platform for CI (ensured to pass on your CI)
- Mocking providers (Riverpod, BLoC, Provider, GetIt)
- Covering edge cases: empty states, errors, loading
- Configuring CI execution with a coverage report (coverage >= 80% on widgets)
Timeframes and Cost
3–5 days for a project with a standard set of screens (10–20 widgets). Golden Tests for the entire UI component library are estimated separately. The cost is calculated individually — contact us to evaluate your project.
Why Trust the Tests to Professionals?
We have dozens of Flutter projects commercially launched on App Store and Google Play. We know the common pitfalls: from overflow to golden mismatches on CI. We guarantee that your widget tests will be stable, fast, and maintainable. Contact us to discuss the details and get a consultation.







