How to `setUp` a `WidgetTester` for Multiple Tests in Flutter
Below is what looks like the current way to solve for this in Flutter.
To summarize:
- Create the
group(..)
structure insidemain()
- Create your own private methods from inside that structure, for each group of testing you want. For each of these private methods:
- Pass in the
WidgetTester
instance - Let them be
async
- Pass in the
- And then you should only have a single call to
testWidgets(..)
- Inside this method, is where you call the private methods you set up to distribute test logic
- Call each of these with
await
, so they don't run concurrently
So far I didn't find a way for the output to indicate each "sub-test" it ran, so just using print(...)
statements for now.
This is a demo for some QR Code logic:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:qr_code_demo/app/appRoutes.dart';
import 'package:qr_code_demo/view/appHome.dart';
import 'package:qr_code_demo/view/qrScanner.dart';
class MockNavigatorObserver extends Mock implements NavigatorObserver {}
void main() {
group('MainPage navigation tests', () {
NavigatorObserver mockObserver;
_loadAppHomeScreen(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
routes: AppRoutes.getRouteMap(),
home: AppHomeScreen(),
navigatorObservers: [mockObserver],
),
);
}
setUp(() {
mockObserver = MockNavigatorObserver();
});
Future<Null> _verifyLayoutElements(WidgetTester tester) async {
print('_verifyLayoutElements');
expect(find.byIcon(Icons.scanner), findsOneWidget);
expect(find.byType(FloatingActionButton), findsOneWidget);
expect(find.byType(RaisedButton), findsOneWidget);
}
Future<Null> _navigateToQrScannerScreen(WidgetTester tester) async {
print('_navigateToQrScannerScreen');
await tester.tap(find.byIcon(Icons.scanner));
await tester.pumpAndSettle();
verify(mockObserver.didPush(any, any));
expect(find.byType(AppHomeScreen), findsNothing);
expect(find.byType(QrScannerScreen), findsOneWidget);
}
testWidgets('AppHomeScreen WidgetTester', (WidgetTester tester) async {
await _loadAppHomeScreen(tester);
await _verifyLayoutElements(tester);
await _navigateToQrScannerScreen(tester);
});
});
}
Thanks to: https://iiro.dev/2018/08/22/writing-widget-tests-for-navigation-events/
- Scroll to code for this file:
test/navigation_test.dart
====
And double-thanks, because the navigation testing logic including with this example is thanks to @iiro's post: https://stackoverflow.com/a/51983194/2162226
Here is the appRoutes.dart
file:
import 'package:qr_code_demo/view/appHome.dart';
import 'package:qr_code_demo/view/qrScanner.dart';
class AppRoutes {
static const String AppHome = 'AppHome';
static const String QrScanner = 'QrScanner';
static String initialRoute() {
return AppHome;
}
static getRouteMap() {
return {
AppRoutes.AppHome: (context) => AppHomeScreen(),
AppRoutes.QrScanner: (context) => QrScannerScreen()
};
}
}
Philippe Fanaro
Graduated as an Electrical/Telecommunications Engineer in 2017, from the University of São Paulo, Brazil. Converted to Machine Learning in 2018. Became a Flutter App Developer in 2019. I'm also a ("retired") Go (Baduk or Weiqi) player, and I also have a website with some curious and interesting stuff: fanaro.io
Updated on December 18, 2022Comments
-
Philippe Fanaro over 1 year
1. The Problem
The
testWidgets
function is apparently only a subcase of thetest
function.A use case I'm trying to solve right now is to pump the same widget for multiple
testWidgets
, asetUp
for multipletestWidgets
. However, how can I do this if it creates a new instance inside each test?I've tried to initialize a
WidgetTester
outside the tests, in themain()
, butWidgetTester
has only a private constructor:class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider { WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) { if (binding is LiveTestWidgetsFlutterBinding) binding.deviceEventDispatcher = this; }
I don't quite get how the Flutter team made this work, but initializing a
WidgetTester
in the same way they did inside thetestWidgets
function isn't working for me:final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding; final WidgetTester tester = WidgetTester._(binding);
2. An Example
A simple example would be to try to break down the tests of the Flutter demo that is created with each new Flutter project from
flutter create
. In it, we could try to separate the initial setup test of the app from the tapping action test:testWidgets('Initial setup', (WidgetTester tester) async { await tester.pumpWidget(MyApp()); expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); }); testWidgets('Increment the counter on tap', (WidgetTester tester) async { await tester.pumpWidget(MyApp()); await tester.tap(find.byIcon(Icons.add)); await tester.pump(); expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); });
The idea would be to try to move the
await tester.pumpWidget(MyApp());
into asetUp
function.