How to write an integration test in Flutter

We all know that no app is maintainable without tests. Indeed, strength of a framework is a function of how easy integration tests are to write and maintain. And I don’t say this lightly – I wrote Android integration tests for a 4.3 rated app with more than one million downloads before Espresso came along!

Flutter offers 3 types of tests: unit, widget, and integration tests. For this code tutorial, I will focus on integration tests – usually the most difficult to write, yet the most rewarding to have.

How it works

Set up

The set up is a bit unusual, in that you need 2 files: one to set up the instrumented app, one for the test. Flutter has adopted a naming convention of descriptionoftest.dart for the first, and descriptionoftest_test.dart for the second. They both must be located in test_driver folder.

To run a test, you use the command line. From your flutter project, run the following command:

It doesn’t seem like a setup that will scale well to large apps, but Flutter is only in alpha, so this may not be the final setup.

How to write tests

Tests are driven by Flutter Driver. The application runs in a separate process from the test itself, and Flutter Driver works in a similar manner to Selenium WebDriver.

At  the start of the test, you connect to the Driver, and at the end, you disconnect (though the API calls this close rather than disconnect).

The driver performs actions on widgets. The ones we will use in this code tutorial are tap, wait, and waitForAbsent, but there are more, so check the official doc for the full list.

The 2 main ways to find widgets are by key and by text, so keys are very important! In Flutter, keys may be global or local. Check the official doc for more information on the topic. In a way, finding a widget by key is similar to finding a view by its id in Android Espresso, but keys are quite flexible because they are set up in dart (as everything in Flutter) and can therefore be set dynamically (particularly useful for lists).

Gotcha!

The example projects do not have many integration tests and it took me a while to figure out why my  test was failing, with the error (or a variation of it) below:

It turned out, you get this error if you import Dart files using the flutter library from your test file. I was doing this because my widget key was defined as a constant in the widget dart file. To get rid of this issue, I put all the string constants used for my widget keys in a separate dart file with no flutter import.

This is a serious problem IMHO because it encourages bad coding behaviour (ie copy and paste strings from the app to the test). Again, the framework is only in alpha, so they may well improve on this.

Code example for integration test

For this code tutorial, we will create a simple app where tapping a button in one widget will amend the content in another widget. Then, we will write an integration test to verify this behaviour.

To follow the code tutorial, create a new app as follows.

If you’re unsure how to set up a Flutter app, check out Getting started with Flutter official tutorial.

Code for app

The app is going to use 3 widgets. The first one is for the whole screen. The second one consists of 2 buttons. One button says “SHOW STORES” and the other says “SHOW PRODUCTS”.  The third one displays a list of items (either stores or products), or shows a message saying “PLEASE SELECT AN OPTION ABOVE”.

Let’s start with the code in main.dart.

Now, let’s create a package list and in it, 3 files, one for each widget.

The first is list_page.dart.

For simplification, the list of stores and products is defined as a simple list of string and is hardcoded.

The second is select_list_view.dart.

And the third is list_view.dart.

Note the imports for key_strings.dart and display_strings.dart. We define the strings for display and the strings for the widget keys in those files, so we can then use them in the tests.

So let’s create those files (in the main lib package), starting with key_strings.dart.

Then display_strings.dart.

Note that those files have no import for the Flutter framework. Additionally, it is good to separate the display strings, so you can then easily localise to other languages.

Depending on your use cases, it is sometimes better to use keys and other times to use display strings for your tests. Generally, keys are better when you have dynamic content, and display strings are better when you have static content.

Code for test

Let’s move on to the tests now. First, we need to add flutter driver to pubspec.yaml.

Then run flutter packages get (or click on “Packages get” in IntelliJ).

Now, we create a test_driver top level folder (ie same level as lib) and a new file list_content.dart in it. We will use it to launch the instrumented version of the app.

Then, we create the file list_content_test.dart in the same folder, and define our first test. In this test, we verify that the empty list view is showing.

Let’s run this test to verify the test set up is working. Make sure you have either a device or emulator connected. Then, in the root folder of your project, type the following command  flutter drive --target=test_driver/list_content.dart

The output should look similar to this.

Then we add another test, to verify that tapping on the first button shows the list of stores. We will check that the first store in the list is visible, the first product in the list is not visible, and the empty message is not visible.

Note how we have moved SerializedFinders to a separate file, to avoid code duplication between test. So create finders.dart in test_driver folder, and add the code below.

Then we add another test, to verify that tapping on the second button shows the list of products.

Running the testS

Now, let’s go to the command line, in your app project, and let’s run the 3 tests.

The output should look something like this.

What next?

Writing tests is something best learnt by doing, so if you haven’t created any Flutter app yet, why not write a test for one of the example apps that come with the framework?

Also, stay tuned, as I plan to write further posts about integration testing on Flutter (as integration testing is a topic I deeply care about!).

Leave a Reply

Your email address will not be published. Required fields are marked *