Integration Testing in Flutter: Getting Started
Learn how to test UI widgets along with the backend services in your Flutter project using Integration Testing. By Monikinderjit Singh.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Integration Testing in Flutter: Getting Started
30 mins
- Getting Started
- Setting Up a Firebase Project
- Setting Up The Android Project
- Setting Up The iOS Project
- Setting Up Authentication and Firestore
- Exploring the Starter Project
- Testing in Flutter
- Comparing Types of Testing
- Examining Unit Testing
- Examining Widget Testing
- Examining Integration Testing
- Setting Up the Project for Testing
- Adding Dependencies
- Creating a Test Directory
- Writing Your First Test
- Diving into LiveTestWidgetsFlutterBinding
- Grouping Tests
- Testing Feature One: Authentication
- Understanding pumpWidget and pumpAndSettle
- Diving Into Widget Keys
- Adding Fake Delays
- Understanding expect()
- Running the Test
- Testing Feature Two: Modifying Ideas
- Inserting New Ideas in a List
- Deleting an Idea
- Where to Go From Here?
Diving Into Widget Keys
Keys help you store and interact directly with widgets by acting as identifiers for them. Use unique keys because non-distinct keys can make widgets work unexpectedly.
These are the different types of widget keys:
- ValueKey: Uses a string as its value.
- ObjectKey: Uses complex object data as its value.
- UniqueKey: A key with a unique value.
- GlobalKey: A key that is globally available in WidgetTree, for example, Form keys.
It’s important to add keys in their correct place. So, open signup_screen.dart and replace //TODO: add value key for signup email textFormField
with:
key: const ValueKey('emailSignUpField'),
This code assigns a constant ValueKey
to the email TextFormField
.
In signup_screen.dart, replace //TODO: add value key for signup password textFormField
with:
key: const ValueKey('passwordSignUpField'),
Here, you assign a constant ValueKey
to the password TextFormField
.
In signup_screen.dart, replace //TODO: add value key for 'Confirm Password' textFormField
with:
key: const ValueKey('confirmPasswordSignUpField'),
Here, you assign a constant ValueKey
to confirm password TextFormField
.
Back in app_test.dart, insert the following code below the previous block:
// previous code
await tester.enterText(
find.byKey(const ValueKey('emailSignUpField')), timeBasedEmail);
//1
await tester.enterText(
find.byKey(const ValueKey('passwordSignUpField')), 'test123');
await tester.enterText(
find.byKey(const ValueKey('confirmPasswordSignUpField')), 'test123');
//2
await tester.tap(find.byType(ElevatedButton));
//TODO: add addDelay() statement here
Here’s what you did:
- The test framework enters Password and confirm details in respective text fields when the SignUp Screen opens.
- Then it taps the ElevatedButton to register the user and triggers a register user event.
Next, you’ll add fake delays to your code.
Adding Fake Delays
You need to add fake delays because integration tests interact with APIs in the real world. As a result, API results fetch late, and the test framework should wait for them. Otherwise, the subsequent statements will require the results from the previous ones, causing the test to fail.
To add a fake delay, insert the following code before main()
:
Future<void> addDelay(int ms) async {
await Future<void>.delayed(Duration(milliseconds: ms));
}
addDelay()
adds fake delays during the testing.
Next, replace //TODO: add addDelay() statement here
with:
await addDelay(24000);
This code adds a delay of 24 seconds.
Insert the following line immediate after the addDelay()
:
await tester.pumpAndSettle();
This code waits for all the animations to complete.
Understanding expect()
You’ve done the work to inject data into your tests. Next it’s important to verify that your tests succeed with the given data. You verify expectations with the expect()
method.
expect()
is an assert method that verifies that the Matcher and expected value match.
Write the following code immediately after the call to pumpAndSettle()
:
expect(find.text('Ideas'), findsOneWidget);
//TODO: call logout function here
Here, you find the text “Ideas” in the UI. The expectation is that there must be only one widget, as you can see in the image above.
findsOneWidget
is a Matcher constant, which means only one widget should be present.
Insert the following code outside main()
:
//1
Future<void> logout(WidgetTester tester) async {
//2
await addDelay(8000);
//3
await tester.tap(find.byKey(
const ValueKey('LogoutKey'),
));
//4
await addDelay(8000);
tester.printToConsole('Login screen opens');
await tester.pumpAndSettle();
}
Here’s a code breakdown:
- You create an asynchronous function for the logout event which helps to make code modular. There’s one argument to this function: a
WidgetTester
object that describes how the current test framework works. - Then you add a fake delay of eight seconds.
- You tap ghd Logout button after a successful signUp.
- Finally, you add a fake delay of eight seconds and print that the login screen opens.
Next, replace //TODO: call the logout function here
with:
await tester.pumpAndSettle();
expect(find.text('Ideas'), findsOneWidget); // previous code
await logout(tester);
This code calls logout()
which makes the user sign out.
Amazing! You’re ready to run your first test.
Running the Test
Build and run the project by typing the following command into your terminal:
flutter test integration_test/app_test.dart
You’ll see:
Congratulations on writing your first integration test!
Now you’re ready to create another test to handle the app’s more complex features and states.
Testing Feature Two: Modifying Ideas
In this section, you’ll learn about complex testing and methods for accessing context.
You’ll follow steps similar to those you did in Testing Feature One: Authentication.
Replace //TODO: add test 2 here
with:
//1
testWidgets('Modifying Features test', (WidgetTester tester) async {
//2
await Firebase.initializeApp();
//3
await tester.pumpWidget(MyApp());
await tester.pumpAndSettle();
//4
await addDelay(10000);
// TODO: add code here
});
Here, you:
- Create a new test with the description
Modifying Features test
. - Then you wait for the test framework to initialize the Firebase app.
- Render your
MyApp
widget to show the login screen and wait for all of the frames to settle. - Add a fake delay of ten seconds, so that the database synchronization can complete.
Next, replace // TODO: add code here
with:
//1
await tester.enterText(find.byKey(const ValueKey('emailLoginField')), timeBasedEmail);
await tester.enterText(find.byKey(const ValueKey('passwordLoginField')), 'test123');
await tester.tap(find.byType(ElevatedButton));
//2
await addDelay(18000);
await tester.pumpAndSettle();
Here’s the explanation for the numbered comments:
- After the login screen opens, you insert the values of email and password in their respective text fields and then tap
ElevatedButton
to trigger the login event. - Add a fake delay of 18 seconds and waiting for all animations to complete.
In the next section, you’ll write code to test adding new ideas to Firestore.
Inserting New Ideas in a List
First, insert this code below the call to pumpAndSettle()
:
//1
await tester.tap(find.byType(FloatingActionButton));
await addDelay(2000);
tester.printToConsole('New Idea screen opens');
await tester.pumpAndSettle();
//2
await tester.enterText(find.byKey(const ValueKey('newIdeaField')), 'New Book');
await tester.enterText(find.byKey(const ValueKey('inspirationField')), 'Elon');
//3
await addDelay(1000);
Here, you:
- Add a new idea to the list by clicking the
FloatingActionButton
on screen. Then the IdeaScreen should open with two text form fields. - Find the text field using
ValueKey
and insert values into it. - Wait for one second to make sure framework finishes entering values.
In the image above, the test fails because the ElevatedButton
is below the keyboard so, the framework won’t find the widget. The next code block solves this issue.
Now, insert this code below the previous block:
await tester.ensureVisible(find.byType(ElevatedButton)); await tester.pumpAndSettle(); await tester.tap(find.byType(ElevatedButton)); //TODO: add code here
ensureVisible()
ensures the widget is visible by scrolling up if necessary.
Replace //TODO: add code here
with:
await addDelay(4000);
tester.printToConsole('New Idea added!');
await tester.pumpAndSettle();
await addDelay(1000);
After submitting the idea, the code waits while the Firestore database updates.
You successfully added the testing code for the new idea feature.
Now, here’s the hard part: deleting an idea. How can you use swipe gestures in automated testing? The next section will explain.