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 2 of 4 of this article. Click here to view the first page.

Setting Up the Project for Testing

To create integration tests for Flutter, you’ll use the integration_test package. In the past, you would have used flutter_driver, but Flutter discontinued it for the following reasons:

  • Difficulty in catching exceptions.
  • Hard interaction with the app components like showBottomSheet.
  • Poor readability of the API.
  • Difficulty in verifying the state of the app.

The integration_test package solves these issues.

Now it’s time to learn how to configure your project to write integration tests. First, you’ll add all the required dependencies.

Adding Dependencies

The Flutter SDK includes integration_test package, so you don’t need to copy it from the Pub.dev website. Instead, you just need to add integration_test to your pubspec.yaml.

Open pubspec.yaml and replace # TODO: add integration test dependency here with:

integration_test:
    sdk: flutter
Note: When adding a dependency, make sure you use the correct indentation, otherwise you’ll get an indentation error.

Don’t forget to run flutter pub get in the terminal.

Now, you’ll create the test directory.

Creating a Test Directory

In the project’s root, create a folder named integration_test. This folder will act as a directory for all of the project’s integration tests.

Integration Test directory

Note: It’s best practice to create a new folder for each different type of test.

Inside the integration_test folder, create a dart file named app_test.dart. This file will include your integration tests.

Now, it’s time to start writing integration tests.

Writing Your First Test

In app_test.dart, insert:

void main() {

}

This function is the first called when running the tests. You’ll write all tests inside this function.

At the top of app_test.dart, insert:

import 'package:integration_test/integration_test.dart';

Here, you import the integration_test package making it ready to use in the file.

Now, inside main(), add:

final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

Here, ensureInitialized() verifies the integration test driver’s initialization. It also reinitializes the driver if it isn’t initialized.

Next, you’ll learn how to use the LiveTestWidgetsFlutterBinding method.

Diving into LiveTestWidgetsFlutterBinding

Insert the following code at the top of app_test.dart:

import 'package:flutter_test/flutter_test.dart';

This code includes the flutter_test package required for configuring the test.

Then, add this code block below the binding variable you defined before:

if (binding is LiveTestWidgetsFlutterBinding) {
  binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
}

LiveTestWidgetsFlutterBindingFramePolicy defines how LiveTestWidgetsFlutterBinding should paint frames. fullyLive is a policy used to show every frame requested by the framework and is best suited for heavy animations.

Checkout the official docs for other policies that might make more sense for your app.

Next, you’ll work on grouping tests.

Grouping Tests

At the last line inside main(), insert:

group('end-to-end test', () {
  //TODO: add random email var here

  //TODO: add test 1 here

  //TODO: add test 2 here
  });

The group() method groups and runs many tests. You include it here since you’ll run multiple tests in the app. It has the following arguments:

  • description: A description of the test group.
  • void Function() body: A function defining what tests to run.
  • skip: An optional argument used to skip the test group. Since it’s dynamic, if the value is String rather than true, it’ll print the value of String when skipping the test.

Now you have all the skills you need to create your first test!

Testing Feature One: Authentication

Most apps start with an authentication screen, and this project is no different. Therefore, you’ll start by writing an authentication test first.

Write the following code in place of //TODO: add random email var here:

final timeBasedEmail = DateTime.now().microsecondsSinceEpoch.toString() + '@test.com'; 

This code creates a time-based email address.

Note: In automated testing, adding time-based/random credentials is useful because you might forget to add a new email when registering a user. Otherwise, you’ll need to either update the test email or delete the email from Firebase authentication every time you run the test. Furthermore, updating the email every time defeats the purpose of automated testing.

Now, replace //TODO: add test 1 here with:

testWidgets('Authentication Testing', (WidgetTester tester) async {
  //TODO: add Firebase Initialization Here
});

testWidgets() lets you define tests for widgets and takes two required parameters:

  • description: Defines what the test is about.
  • callback function: A function that executes during the test. It takes a WidgetTester object as a parameter. This WidgetTester object interacts with the widgets and the test environment.

The callback function is asynchronous because your test will interact with real-world APIs.

testWidgets() also has some optional parameters like skip and timeout.

timeout is the maximum time required to run the test. After that time, the test will fail automatically. It’s ten minutes by default.

Insert the following lines at the top of app_test.dart:

import 'package:firebase_core/firebase_core.dart';

This code lets you initialize your Firebase app during the test.

Replace //TODO: add Firebase Initialization Here with:

await Firebase.initializeApp();

This code ensures your app is ready to use Firebase services.

Now, you’ll work with pumpWidget and pumpAndSettle.

Understanding pumpWidget and pumpAndSettle

At the top of app_test.dart, add:

//1
import 'package:ideast/main.dart';
//2
import 'package:flutter/material.dart';

Here’s a code breakdown:

  1. Imports main.dart to get access to MyApp().
  2. Imports material.dart to access Flutter widgets.

Now, below the code you just added, add:

await Firebase.initializeApp(); // previous code
await tester.pumpWidget(MyApp());
await tester.pumpAndSettle();

//TODO: Add here

Here’s an explanation of the code:

duration is the most suitable option when you know how many frames will render, such as navigation without animations.

  1. pumpWidget() renders the UI of the provided widget. Here you pass MyApp() as the rendering widget. PumpWidget also takes duration as a parameter, which will shift the fake clock by the specified duration to help you avoid excessive frame rates.

    duration is the most suitable option when you know how many frames will render, such as navigation without animations.

  2. pumpAndSettle() repeatedly calls pump for a given duration until there are no frames to settle, which is usually required when you have some animations. pumpAndSettle is called after pumpWidget() because you want to wait for the navigation animations to complete.

Replace //TODO: Add code here with:

await tester.tap(find.byType(TextButton));
//TODO: Add code here

tap() is a method in WidgetTester that lets you tap the centre of the widget. This requires a Finder to tell the framework to tap it.

The byType property defines the type of widget. TextButton and ElevatedButton are acceptable but not abstract classes such as StatefulWidget.

Replace //TODO: Add code here with:

//1
tester.printToConsole('SignUp screen opens');
//2
await tester.pumpAndSettle();
//3
await tester.enterText(find.byKey(const ValueKey('emailSignUpField')), timeBasedEmail);

Here’s a code breakdown:

  1. printToConosle prints statements during the test. Including a few descriptive statements provides a sense comfort that the tests are running.
  2. Waits for all animations to settle down.
  3. In the text field, you can enter text using the tester’s enterText property. There are two parameters: a Finder and a String. String enters this text into the Text field. byKey finds widgets using their widget keys.

In the next section, you’ll learn how to interact with the widgets using keys.