There are 3 types of tests in Flutter:
From the listed tests, we will analyze the Widget tests. The goal is to test the widget since everything is a widget in a Flutter.
Tests must be:
For test widgets, the testWidgets
method is presented with the main parameters description, where you can write a description in string format, and the next parameter class WidgetTester
is a class that programmatically interacts with widgets and the test environment.
testWidgets('Test description', (WidgetTester widgetTester) async {
//...
});
Take, for example, the code below, with an input field and Sing In button:
class SignInScreen extends StatefulWidget {
const SignInScreen({super.key});
@override
State<StatefulWidget> createState() => _SignInScreenState();
}
class _SignInScreenState extends State<SignInScreen> {
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Check email'),),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextField(
controller: _textEditingController,
key: const ValueKey('email_field'),
decoration: const InputDecoration(hintText: 'Enter e-mail'),
),
const SizedBox(height: 24,),
ElevatedButton(onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => const SuccessScreen()));
}, child: const Text('Sing In'))
],
),
),
);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Widget test',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const SignInScreen(),
);
}
}
And let's write a widget test:
void main() {
testWidgets('Widget test', (WidgetTester widgetTester) async {
await widgetTester.pumpWidget(const MaterialApp(home: SignInScreen(),));
Finder title = find.text('Check email');
expect(title, findsOneWidget);
});
testWidgets('Test input field', (WidgetTester widgetTester) async {
await widgetTester.pumpWidget(const MaterialApp(home: SignInScreen(),));
Finder emailTextField = find.byKey(const ValueKey('email_field'));
expect(emailTextField, findsOneWidget);
});
testWidgets('Test sign in button', (WidgetTester widgetTester) async {
await widgetTester.pumpWidget(const MaterialApp(home: SignInScreen(),));
Finder signInButton = find.byType(ElevatedButton);
expect(signInButton, findsOneWidget);
});
}
What the pumpWidget
method does is render the passed widget. Next, using Finder
we look for the widget we need. You can search in different ways and using different finders. Here are the most common ones (there are many more). You can also easily write your finder
.
find.text() - searches for text;
find.byKey() - searches for a widget by key;
find.byType() - searches for a widget by type;
find.byIcon() - searches for a widget of type Icon;
find.byWidgetPredicate() - searches for a widget by predicate;
In the example above, I use three methods for the title using the text find.text('Check email')
, for the input field using the key find.byKey(const ValueKey('email_field'))
, and for the button using the type find.byType( ElevatedButton)
.
All that remains is to compare the resulting result with the given finders using the Matcher
class. There are also a lot of them written for almost every occasion in life. But you can easily write your own.
Below are the most, in my opinion, used:
findsOneWidget - compares that Finder
finds only one widget;
findsWidgets - compares that Finder
finds at least one widget;
isSameColorAs(Color color) - compares that an object has a certain color;
findsNothing - compares that Finder
does not find the widget;
isNotNull - compares that the object is not null;
When we run the test and there is an error, we get the following result:
Here, I deliberately omitted a letter to show an error.
If the test is passed successfully, we get the following result:
Thank you for your attention!