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.

5 (4) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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.

Widget 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:

  1. The test framework enters Password and confirm details in respective text fields when the SignUp Screen opens.
  2. 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.

Note: This delay may be different for your device type depending on the processor and internet quality. So, you can increase or decrease the duration accordingly.

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

Expect method

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:

  1. 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.
  2. Then you add a fake delay of eight seconds.
  3. You tap ghd Logout button after a successful signUp.
  4. 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:

Authentication Integration Test Flutter

Congratulations on writing your first integration test!

Flutter congratulations

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:

  1. Create a new test with the description Modifying Features test.
  2. Then you wait for the test framework to initialize the Firebase app.
  3. Render your MyApp widget to show the login screen and wait for all of the frames to settle.
  4. 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:

  1. 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.
  2. 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:

  1. Add a new idea to the list by clicking the FloatingActionButton on screen. Then the IdeaScreen should open with two text form fields.
  2. Find the text field using ValueKey and insert values into it.
  3. Wait for one second to make sure framework finishes entering values.

Invisible widget Flutter

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.

Test completing happiness

Now, here’s the hard part: deleting an idea. How can you use swipe gestures in automated testing? The next section will explain.

Creature with a monacle, thinking