Assertions in Dart and Flutter tests: Flutter widgets matchers
This is the part of the ultimate cheat sheet dedicated to:
- no / one / N widgets matchers,
- color matcher,
- parent matchers,
- error matchers.
In this series:
- expect and matcher
- equality matchers
- type and error matchers
- collection matchers
- numeric and comparable matchers
- universal and custom matchers
- matcher operators
- asynchronous expect and matchers
- Flutter widget matchers
- accessibility matchers
- golden matchers
- mock invocations and parameters
Flutter widget tests use the same expect()
method to verify the actual widget tree content matches expectations. However, the list of matchers is unique to this task.
No / One / N widgets matchers
This group of matchers is the most commonly used in Flutter widget tests.
findsNothing
ensures no widget in the widget tree matches the first parameter of the expect()
method:
testWidgets('widget test: findsNothing β
', (tester) async {
final content = Text('1');
await tester.pumpWidget(MaterialApp(home: Scaffold(body: content)));
expect(find.text('0'), findsNothing);
});
The findsWidgets
/findsOneWidget
matchers ensure at least/exactly one widget is present in the tree:
testWidgets('widget test: findsWidgets β
', (tester) async {
final content = Column(children: [Text('1'), Text('1')]);
await tester.pumpWidget(MaterialApp(home:Scaffold(body: content)));
expect(find.text('1'), findsWidgets);
});
testWidgets('widget test: findsOneWidget β', (tester) async {
final content = Column(children: [Text('1'), Text('1')]);
await tester.pumpWidget(MaterialApp(home:Scaffold(body: content)));
expect(find.text('1'), findsOneWidget);
});
In the example above, findsOneWidget
matcher failed the test because the widget tree contains two widgets with the text '1'
. The test output is:
Expected: exactly one matching node in the widget tree
Actual: _TextFinder:<2 widgets with text "1" (ignoring offstage widgets): [
Text("1", dependencies:[DefaultSelectionStyle, DefaultTextStyle, MediaQuery]),
Text("1", dependencies:[DefaultSelectionStyle, DefaultTextStyle, MediaQuery])]>
Which: is too many
The findsAtLeastNWidgets
/findsNWidgets
matchers ensure at least/exactly N widgets are present in the widgets tree.
testWidgets('widget test: findsAtLeastNWidgets β
', (tester) async {
final content = Column(children: [Text('1'), Text('1')]);
await tester.pumpWidget(MaterialApp(home:Scaffold(body: content)));
expect(find.text('1'), findsAtLeastNWidgets(2));
});
testWidgets('widget test: findsNWidgets β
', (tester) async {
final content = Column(children: [Text('1'), Text('1')]);
await tester.pumpWidget(MaterialApp(home:Scaffold(body: content)));
expect(find.text('1'), findsNWidgets(2));
});
Color matcher
isSameColorAs
helps verify properties of Color
type of any widget:
testWidgets('widget test: isSameColorAs β
', (tester) async {
final content = Container(color: Colors.green);
await tester.pumpWidget(MaterialApp(home: Scaffold(body: content)));
expect(
tester.widget<Container>(find.byType(Container)).color,
isSameColorAs(Colors.green),
);
});
testWidgets('widget test: isSameColorAs β
', (tester) async {
final content = Text('1', style: TextStyle(color: Colors.green));
await tester.pumpWidget(MaterialApp(home: Scaffold(body: content)));
expect(
tester.widget<Text>(find.text('1')).style!.color,
isSameColorAs(Colors.green),
);
});
Parent matchers
isInCard
/isNotInCard
helps to verify that widget has at least one/no Card
widget ancestor:
testWidgets('widget test: isInCard β
', (tester) async {
final content = Card(child: Text('1'));
await tester.pumpWidget(MaterialApp(home: Scaffold(body: content)));
expect(find.text('1'), isInCard);
});
testWidgets('widget test: isNotInCard β
', (tester) async {
final content = Text('1');
await tester.pumpWidget(MaterialApp(home: Scaffold(body: content)));
expect(find.text('1'), isNotInCard);
});
Error matchers
We all know that the Container
widget cannot accept both color
and decoration
parameters because of the following assert
in its constructor:
assert(color == null || decoration == null,
'Cannot provide both a color and a decoration\\n'
'To provide both, use "decoration: BoxDecoration(color: color)".',
),
If you take a similar approach when implementing your widgets, the throwsAssertionError
matcher can help with testing these assert
s:
testWidgets('widget test: throwsAssertionError β
', (tester) async {
final builder = () => Container(color: Colors.red, decoration: BoxDecoration(color: Colors.red));
expect(() => builder(), throwsAssertionError);
});
Because throwsAssertionError
uses throwsA
matcher under the hood, the same principle applies here: the function that is expected to throw should be called under the expect()
method call.
The throwsFlutterError
matcher verifies that a function throws FlutterError
. Here is an example from the Flutter framework tests:
testWidgets('widget test: throwsFlutterError β
', (tester) async {
final testKey = GlobalKey<NavigatorState>();
await tester.pumpWidget(SizedBox(key: testKey));
expect(() => Navigator.of(testKey.currentContext!), throwsFlutterError);
});
Originally published at Invertase blog. Check out their awesome Authors Program!
Hi! ππ» Iβm Anna, Google Developer Expert in Flutter from Ukraine πΊπ¦ Follow me on Twitter, GitHub, YouTube, Medium to get notifications about my latest work.
Itβs early 2023, and we in Ukraine are still fighting against russians committing genocide on our lands. If you find this content useful and have a coin to spare, support us with your donations. Stand with Ukraine!