Testing In Flutter: Widget Test
What is a Widget test? What are pumpWidget(), pump() & pumpAndSettle()? How to test smaller widgets in Isolation?

Search for a command to run...
What is a Widget test? What are pumpWidget(), pump() & pumpAndSettle()? How to test smaller widgets in Isolation?

That's nice, is there a why to automate the Flutter website? currently my code base is commonly used for both Mobile & Website
Lessons from Code, Content, and Consistency

Reliability basics: Outbox pattern + why it matters

Introducing Kafka/Redpanda + move to event-driven workflow

Connect Orders ↔ Inventory (first working service-to-service flow)

Inventory service + gRPC + proto contracts

flutter_test package, we can utilize several handy tools and utilities to interact with our application.test folder if you don't already have one.calculator_app_test.dart, in which we'll write our test.flutter_test package so we can use the testWidgets() method instead of the previous test() method that we use for unit test.import 'package:flutter_test/flutter_test.dart';
void main() {
group(' ', () {
testWidgets(' ',
(WidgetTester tester) async {
}
);
});
}
import 'package:flutter/material.dart';
void main() {
runApp(const CalculatorPage());
}
class CalculatorPage extends StatelessWidget {
const CalculatorPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text("Calculator"),
),
body: Column(
children: const [
ListTile(
title: Text("Addition"),
leading: Icon(Icons.add),
),
ListTile(
title: Text("Subtraction"),
leading: Icon(Icons.minimize),
),
ListTile(
title: Text("Multiplication"),
leading: Icon(Icons.cancel_outlined),
),
ListTile(
title: Text("Division"),
leading: Icon(Icons.architecture),
),
],
),
),
);
}
}
calculator app test.dart file.void main() {
group('CalculatorApp', () {
});
}
testWidgets method. testWidgets is like test in unit testing, but for widget testing.void main() {
group('CalculatorApp', () {
testWidgets('Render 4 widgets of Type ListTile',
(WidgetTester tester) async {
});
});
}
WidgetTester, which is used to interact with the application.runApp to inflate our application and attach it to the screen of the device.pumpWidget method from the widget tester class achieves the same. On a test environment it renders the initial UI of the given widget.void main() {
group('CalculatorApp', () {
testWidgets('Render 4 widgets of Type ListTile',
(WidgetTester tester) async {
await tester.pumpWidget(const CalculatorPage());
expect(find.byType(ListTile), findsNWidgets(4)); // Expecting 4 ListTile widget on Screen.
});
});
}

pumpWidget(), pump() & pumpAndSettle()Textfield for inputting two numbers for calculation. I also gave each of the Textfield a unique Key So that we can find that particular widget inside our tests.
add. And then using pumpWidget let’s render the CalculatorPage. group('add',(){
testWidget('Show result when two inputs are given',(WidgetTester tester) async{
await tester.pumpWidget(const CalculatorPage())
})
})
TextField. In order to find the particular TextField, I’ve assigned a unique Key to each of them.WidgetTester provides us a method called enterText(). group('add',(){
testWidget('Show result when two inputs are given',(WidgetTester tester) async{
await tester.pumpWidget(const CalculatorPage())
await tester.enterText(find.byKey(const Key('textfield_top_plus')), '3');
await tester.enterText(find.byKey(const Key('textfield_bottom_plus')), '6');
})
})
Finder: This must be an EditableText or have an EditableText descendant. For example TextField or TextFormField , or EditableText.String: It is basically the value which we want to be entered in the Field.Result: 9 or not.find.text() function to obtain the Text, and then use findsOneWidget to determine whether or not any widget has the expected string.group('add', () {
testWidgets('Show result when two inputs are given',
(WidgetTester tester) async {
await tester.pumpWidget(const CalculatorPage());
await tester.enterText(find.byKey(const Key('textfield_top_plus')), '3');
await tester.enterText(find.byKey(const Key('textfield_bottom_plus')), '6');
expect(find.text('Result: 9.0'), findsOneWidget);
});
});
zero widgets with the text "Result: 9.0". This means none were found but one was expected. Why did this happen?pump() method.pump() instructs the system to paint a new frame so that we can meet our expectations with a newly updated user interface.group('add', () {
testWidgets('Show result when two inputs are given',
(WidgetTester tester) async {
await tester.pumpWidget(const CalculatorPage());
await tester.enterText(find.byKey(const Key('textfield_top_plus')), '3');
await tester.enterText(
find.byKey(const Key('textfield_bottom_plus')), '6');
await tester.pump();
expect(find.text('Result: 9.0'), findsOneWidget);
});
});
pump() is rather restricted. Because it just refreshes a single frame, which is useless when working with animations.resultAfterAnimation, and I'm setting its value when the animation has finished.AnimatedContainer(
padding: const EdgeInsets.all(8),
duration: const Duration(milliseconds: 1000),
onEnd: () {
setState(() {
resultAfterAnimation = result.toString();
});
},
color: result == null ? Colors.transparent : Colors.green,
curve: Curves.easeInOut,
child: Text(
resultAfterAnimation != null
? 'Result: $resultAfterAnimation'
: 'Result: ',
style: Theme.of(context).textTheme.bodyText1,
textAlign: TextAlign.end,
),
),
pumpAndSettle() function provided by the WidgetTest.pump() method with pumpAndSettle() and you're done.group('add', () {
testWidgets('Show result when two inputs are given',
(WidgetTester tester) async {
await tester.pumpWidget(const CalculatorPage());
await tester.enterText(find.byKey(const Key('textfield_top_plus')), '3');
await tester.enterText(
find.byKey(const Key('textfield_bottom_plus')), '6');
await tester.pumpAndSettle();
expect(find.text('Result: 9.0'), findsOneWidget);
});
});
CommonFindersbyKey().byType(): If we know the class name of the widget that we want to locate orbyText(): If what we want to locate is a certain string on the screenWidgetTestertestWidget() method provides us a callback that gives us tester instance of a WidgetTester.pumpWidget(). (In our case that is CalculatorPage()) .tester that ensureVisible() to make sure that a given widget is visible within a scrollable view.tester.tap() on TextFormField so they can gain focus.And tester.enterText() to type the given text in the text field .
group('add', () {
testWidgets('Show result when two inputs are given',
(WidgetTester tester) async {
await tester.pumpWidget(const CalculatorPage());
final topTextFieldFinder = find.byKey(const Key('textfield_top_plus'));
final bottomTextFieldFinder = find.byKey(const Key('textfield_bottom_plus'));
await tester.ensureVisible(topTextFieldFinder);
await tester.tap(topTextFieldFinder);
await tester.enterText(topTextFieldFinder, '3');
await tester.ensureVisible(bottomTextFieldFinder);
await tester.tap(bottomTextFieldFinder);
await tester.enterText(bottomTextFieldFinder, '6');
await tester.pumpAndSettle();
expect(find.text('Result: 9.0'), findsOneWidget);
});
});
OperationWidget.
pumpWidget().OperationWidget has five dependencies in its constructor, the icon, title, two keys, and the operation enum.OperationWidget using pumpWidget and all the other things remain the same.main() {
group('TwoDigit Addition Operation', () {
testWidgets('render 10 when 5 and 5 added', (tester) async {
final topTextFieldFinder = find.byKey(const Key('textfield_top_plus'));
final bottomTextFieldFinder =
find.byKey(const Key('textfield_bottom_plus'));
await tester.pumpWidget(
OperationWidget(
operationIcon: Icons.add,
operationTitle: "Addition",
operationType: OperationType.add,
textFieldTopKey: 'textfield_top_plus',
textFieldBottomKey: 'textfield_bottom_plus',
),
);
await tester.enterText(topTextFieldFinder, '5');
await tester.enterText(bottomTextFieldFinder, '5');
await tester.pumpAndSettle();
expect(find.text('Result: 10.0'), findsOneWidget);
});
});
}
The specific widget that could not find a Material ancestor was: TextField.OperationWidget around MaterialApp and Scaffold widget.main() {
group('TwoDigit Addition Operation', () {
//...
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: OperationWidget(
operationIcon: Icons.add,
operationTitle: "Addition",
operationType: OperationType.add,
textFieldTopKey: 'textfield_top_plus',
textFieldBottomKey: 'textfield_bottom_plus',
),
),
),
);
//...
});
});
}
