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:

How to run a test flutter drive --target=test_driver/descriptionoftest.dart 1 flutter drive -- target = test_driver / descriptionoftest . dart

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.

Looking for a Flutter job? Check out my job board dedicated to Flutter at flutterjobs.info

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:

Error message The built-in library 'dart:ui' is not available on the stand-alone VM. 1 The built - in library 'dart:ui' is not available on the stand - alone VM .

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.

create new app flutter create integrationtestexample 1 flutter create integrationtestexample

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.

main.dart import 'package:flutter/material.dart'; import 'list/list_page.dart'; void main() { runApp(new MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return new MaterialApp( title: 'IntegrationTest Example', theme: new ThemeData( primaryColor: const Color(0xFF43a047), accentColor: const Color(0xFFffcc00), primaryColorBrightness: Brightness.dark, ), home: new ListPage(), ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import 'package:flutter/material.dart' ; import 'list/list_page.dart' ; void main ( ) { runApp ( new MyApp ( ) ) ; } class MyApp extends StatelessWidget { // This widget is the root of your application. @ override Widget build ( BuildContext context ) { return new MaterialApp ( title : 'IntegrationTest Example' , theme : new ThemeData ( primaryColor : const Color ( 0xFF43a047 ) , accentColor : const Color ( 0xFFffcc00 ) , primaryColorBrightness : Brightness . dark , ) , home : new ListPage ( ) , ) ; } }

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

The first is list_page.dart.

list/list_page.dart import 'package:flutter/material.dart'; import 'select_list_view.dart'; import 'list_view.dart'; import '../key_strings.dart'; class ListPage extends StatefulWidget { ListPage({Key key}) : super(key: key); @override _ListPageState createState() => new _ListPageState(); } class _ListPageState extends State<ListPage> { List<String> _stores; List<String> _products; List<String> _selectedItems; String _selectedType; @override void initState() { super.initState(); _selectedItems = null; _selectedType = ''; // TODO - this is shortcut to specify products and stores. // In practice, you should load this from a data repository. _stores = new List<String>(); _stores.add('London'); _stores.add('Paris'); _stores.add('Atlanta'); _products = new List<String>(); _products.add('Laptop'); _products.add('Monitor'); } @override Widget build(BuildContext context) { Widget buttonsWidget = new SelectListView(showProducts: _showProducts, showStores: _showStores); Widget itemsWidget = new ItemsListView(typeId: _selectedType, items: _selectedItems); return new Scaffold( appBar: new AppBar( title: new Text("List of items"), ), body: new Padding( padding: new EdgeInsets.symmetric(vertical: 0.0, horizontal: 4.0), child: new Column(children: <Widget>[ buttonsWidget, new Expanded( child: itemsWidget, ), ], ), ), ); } void _showProducts() { setState(() { _selectedItems = _products; _selectedType = PRODUCT_TYPE; }); } void _showStores() { setState(() { _selectedItems = _stores; _selectedType = STORE_TYPE; }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 import 'package:flutter/material.dart' ; import 'select_list_view.dart' ; import 'list_view.dart' ; import '../key_strings.dart' ; class ListPage extends StatefulWidget { ListPage ( { Key key } ) : super ( key : key ) ; @ override _ListPageState createState ( ) = > new _ListPageState ( ) ; } class _ListPageState extends State < ListPage > { List < String > _stores ; List < String > _products ; List < String > _selectedItems ; String _selectedType ; @ override void initState ( ) { super . initState ( ) ; _selectedItems = null ; _selectedType = '' ; // TODO - this is shortcut to specify products and stores. // In practice, you should load this from a data repository. _stores = new List < String > ( ) ; _stores . add ( 'London' ) ; _stores . add ( 'Paris' ) ; _stores . add ( 'Atlanta' ) ; _products = new List < String > ( ) ; _products . add ( 'Laptop' ) ; _products . add ( 'Monitor' ) ; } @ override Widget build ( BuildContext context ) { Widget buttonsWidget = new SelectListView ( showProducts : _showProducts , showStores : _showStores ) ; Widget itemsWidget = new ItemsListView ( typeId : _selectedType , items : _selectedItems ) ; return new Scaffold ( appBar : new AppBar ( title : new Text ( "List of items" ) , ) , body : new Padding ( padding : new EdgeInsets . symmetric ( vertical : 0.0 , horizontal : 4.0 ) , child : new Column ( children : < Widget > [ buttonsWidget , new Expanded ( child : itemsWidget , ) , ] , ) , ) , ) ; } void _showProducts ( ) { setState ( ( ) { _selectedItems = _products ; _selectedType = PRODUCT_TYPE ; } ) ; } void _showStores ( ) { setState ( ( ) { _selectedItems = _stores ; _selectedType = STORE_TYPE ; } ) ; } }

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.

list/select_list_view.dart import 'package:flutter/material.dart'; import '../display_strings.dart'; class SelectListView extends StatelessWidget { SelectListView({Key key, this.showProducts, this.showStores}) : super(key: key); Function showProducts; Function showStores; @override Widget build(BuildContext context) { return new Container( child: new Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ new FlatButton( textColor: Colors.blueGrey, color: Colors.white, child: new Text(SHOW_STORES), onPressed: showStores, ), new FlatButton( textColor: Colors.blueGrey, color: Colors.white, child: new Text(SHOW_PRODUCTS), onPressed: showProducts, ), ], ), ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import 'package:flutter/material.dart' ; import '../display_strings.dart' ; class SelectListView extends StatelessWidget { SelectListView ( { Key key , this . showProducts , this . showStores } ) : super ( key : key ) ; Function showProducts ; Function showStores ; @ override Widget build ( BuildContext context ) { return new Container ( child : new Row ( mainAxisAlignment : MainAxisAlignment . spaceEvenly , children : [ new FlatButton ( textColor : Colors . blueGrey , color : Colors . white , child : new Text ( SHOW_STORES ) , onPressed : showStores , ) , new FlatButton ( textColor : Colors . blueGrey , color : Colors . white , child : new Text ( SHOW_PRODUCTS ) , onPressed : showProducts , ) , ] , ) , ) ; } }

And the third is list_view.dart.

list/list_view.dart import 'package:flutter/material.dart'; import '../key_strings.dart'; import '../display_strings.dart'; class ItemsListView extends StatelessWidget { ItemsListView({Key key, this.items, this.typeId}) : super(key: key); List<String> items; String typeId; @override Widget build(BuildContext context) { if (items != null) { return new ListView( scrollDirection: Axis.vertical, shrinkWrap: true, children: items.map((String value) { return _singleItemDisplay(value, items.indexOf(value)); }).toList()); } else { return new Text(EMPTY); } } Widget _singleItemDisplay(String item, int index) { return new Container( key: new Key(getStringKeyForListItem(typeId, index)), height: 40.0, child: new Container ( padding: const EdgeInsets.all(2.0), color: new Color(0x33000000), child: new Text(item), ), ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import 'package:flutter/material.dart' ; import '../key_strings.dart' ; import '../display_strings.dart' ; class ItemsListView extends StatelessWidget { ItemsListView ( { Key key , this . items , this . typeId } ) : super ( key : key ) ; List < String > items ; String typeId ; @ override Widget build ( BuildContext context ) { if ( items != null ) { return new ListView ( scrollDirection : Axis . vertical , shrinkWrap : true , children : items . map ( ( String value ) { return _singleItemDisplay ( value , items . indexOf ( value ) ) ; } ) . toList ( ) ) ; } else { return new Text ( EMPTY ) ; } } Widget _singleItemDisplay ( String item , int index ) { return new Container ( key : new Key ( getStringKeyForListItem ( typeId , index ) ) , height : 40.0 , child : new Container ( padding : const EdgeInsets . all ( 2.0 ) , color : new Color ( 0x33000000 ) , child : new Text ( item ) , ) , ) ; } }

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.

key_strings.dart const String PRODUCT_TYPE = 'PRODUCT_'; const String STORE_TYPE = 'STORE_'; String getStringKeyForListItem(String type, int index) { return type + index.toString(); } 1 2 3 4 5 6 const String PRODUCT_TYPE = 'PRODUCT_' ; const String STORE_TYPE = 'STORE_' ; String getStringKeyForListItem ( String type , int index ) { return type + index . toString ( ) ; }

Then display_strings.dart.

display_strings.dart const String SHOW_STORES = 'SHOW STORES'; const String SHOW_PRODUCTS = 'SHOW PRODUCTS'; const String EMPTY = 'PLEASE SELECT AN OPTION ABOVE'; 1 2 3 const String SHOW_STORES = 'SHOW STORES' ; const String SHOW_PRODUCTS = 'SHOW PRODUCTS' ; const String EMPTY = 'PLEASE SELECT AN OPTION ABOVE' ;

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.

pubspec.yaml dev_dependencies: flutter_test: sdk: flutter flutter_driver: sdk: flutter 1 2 3 4 5 dev_dependencies : flutter_test : sdk : flutter flutter_driver : sdk : flutter

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.

test_driver/list_content.dart import 'package:flutter_driver/driver_extension.dart'; import 'package:integrationtestexample/main.dart' as app; void main() { enableFlutterDriverExtension(); app.main(); } 1 2 3 4 5 6 7 import 'package:flutter_driver/driver_extension.dart' ; import 'package:integrationtestexample/main.dart' as app ; void main ( ) { enableFlutterDriverExtension ( ) ; app . main ( ) ; }

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.

test_driver/list_content_test.dart import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; import 'package:integrationtestexample/display_strings.dart'; void main() { group('list content test', () { FlutterDriver driver; setUpAll(() async { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) driver.close(); }); test('Verify empty list message is shown', () async { SerializableFinder emptyMessage = find.text(EMPTY); await driver.waitFor(emptyMessage); }); }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import 'package:flutter_driver/flutter_driver.dart' ; import 'package:test/test.dart' ; import 'package:integrationtestexample/display_strings.dart' ; void main ( ) { group ( 'list content test' , ( ) { FlutterDriver driver ; setUpAll ( ( ) async { driver = await FlutterDriver . connect ( ) ; } ) ; tearDownAll ( ( ) async { if ( driver != null ) driver . close ( ) ; } ) ; test ( 'Verify empty list message is shown' , ( ) async { SerializableFinder emptyMessage = find . text ( EMPTY ) ; await driver . waitFor ( emptyMessage ) ; } ) ; } ) ; }

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.

Test output Using device HTC One M8. Starting application: test_driver/list_content.dart Initializing gradle... 0.4s Resolving dependencies... 0.6s Installing build/app/outputs/apk/app.apk... 11.1s Running 'gradlew assembleDebug'... 5.9s Built build/app/outputs/apk/app-debug.apk (21.5MB). Installing build/app/outputs/apk/app.apk... 6.0s I/flutter ( 714): Diagnostic server listening on http://127.0.0.1:43061/ I/flutter ( 714): Observatory listening on http://127.0.0.1:34552/ 00:00 +0: list content test (setUpAll) [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:8104/ [trace] FlutterDriver: Looking for the isolate [trace] FlutterDriver: Isolate is paused at start. [trace] FlutterDriver: Attempting to resume isolate [trace] FlutterDriver: Waiting for service extension [info ] FlutterDriver: Connected to Flutter application. 00:02 +0: list content test Verify empty list message is shown 00:02 +1: list content test (tearDownAll) 00:03 +1: All tests passed! Stopping application instance. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Using device HTC One M8 . Starting application : test_driver / list_content . dart Initializing gradle . . . 0.4s Resolving dependencies . . . 0.6s Installing build / app / outputs / apk / app . apk . . . 11.1s Running 'gradlew assembleDebug' . . . 5.9s Built build / app / outputs / apk / app - debug . apk ( 21.5MB ) . Installing build / app / outputs / apk / app . apk . . . 6.0s I / flutter ( 714 ) : Diagnostic server listening on http : //127.0.0.1:43061/ I / flutter ( 714 ) : Observatory listening on http : //127.0.0.1:34552/ 00 : 00 + 0 : list content test ( setUpAll ) [ info ] FlutterDriver : Connecting to Flutter application at http : //127.0.0.1:8104/ [ trace ] FlutterDriver : Looking for the isolate [ trace ] FlutterDriver : Isolate is paused at start . [ trace ] FlutterDriver : Attempting to resume isolate [ trace ] FlutterDriver : Waiting for service extension [ info ] FlutterDriver : Connected to Flutter application . 00 : 02 + 0 : list content test Verify empty list message is shown 00 : 02 + 1 : list content test ( tearDownAll ) 00 : 03 + 1 : All tests passed ! Stopping application instance .

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.

test_driver/list_content_test.dart import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; import 'finders.dart'; void main() { group('list content test', () { FlutterDriver driver; setUpAll(() async { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) driver.close(); }); test('Verify empty list message is shown', () async { await driver.waitFor(emptyMessage); }); test('Tap show stores button, verify stores shown', () async { await driver.tap(showStores); await driver.waitFor(firstStore); await driver.waitForAbsent(firstProduct); await driver.waitForAbsent(emptyMessage); }); }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import 'package:flutter_driver/flutter_driver.dart' ; import 'package:test/test.dart' ; import 'finders.dart' ; void main ( ) { group ( 'list content test' , ( ) { FlutterDriver driver ; setUpAll ( ( ) async { driver = await FlutterDriver . connect ( ) ; } ) ; tearDownAll ( ( ) async { if ( driver != null ) driver . close ( ) ; } ) ; test ( 'Verify empty list message is shown' , ( ) async { await driver . waitFor ( emptyMessage ) ; } ) ; test ( 'Tap show stores button, verify stores shown' , ( ) async { await driver . tap ( showStores ) ; await driver . waitFor ( firstStore ) ; await driver . waitForAbsent ( firstProduct ) ; await driver . waitForAbsent ( emptyMessage ) ; } ) ; } ) ; }

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.

test_driver/finders.dart import 'package:integrationtestexample/display_strings.dart'; import 'package:flutter_driver/flutter_driver.dart'; import 'package:integrationtestexample/key_strings.dart'; SerializableFinder emptyMessage = find.text(EMPTY); SerializableFinder showStores = find.text(SHOW_STORES); SerializableFinder showProducts = find.text(SHOW_PRODUCTS); SerializableFinder firstStore = find.byValueKey(getStringKeyForListItem(STORE_TYPE, 0)); SerializableFinder firstProduct = find.byValueKey(getStringKeyForListItem(PRODUCT_TYPE, 0)); 1 2 3 4 5 6 7 8 9 10 11 import 'package:integrationtestexample/display_strings.dart' ; import 'package:flutter_driver/flutter_driver.dart' ; import 'package:integrationtestexample/key_strings.dart' ; SerializableFinder emptyMessage = find . text ( EMPTY ) ; SerializableFinder showStores = find . text ( SHOW_STORES ) ; SerializableFinder showProducts = find . text ( SHOW_PRODUCTS ) ; SerializableFinder firstStore = find . byValueKey ( getStringKeyForListItem ( STORE_TYPE , 0 ) ) ; SerializableFinder firstProduct = find . byValueKey ( getStringKeyForListItem ( PRODUCT_TYPE , 0 ) ) ;

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

test_driver/list_content_test.dart import 'package:flutter_driver/flutter_driver.dart'; import 'package:test/test.dart'; import 'finders.dart'; void main() { group('list content test', () { FlutterDriver driver; setUpAll(() async { driver = await FlutterDriver.connect(); }); tearDownAll(() async { if (driver != null) driver.close(); }); test('Verify empty list message is shown', () async { await driver.waitFor(emptyMessage); }); test('Tap show stores button, verify stores shown', () async { await driver.tap(showStores); await driver.waitFor(firstStore); await driver.waitForAbsent(firstProduct); await driver.waitForAbsent(emptyMessage); }); test('Tap show products button, verify products shown', () async { await driver.tap(showProducts); await driver.waitFor(firstProduct); await driver.waitForAbsent(firstStore); await driver.waitForAbsent(emptyMessage); }); }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import 'package:flutter_driver/flutter_driver.dart' ; import 'package:test/test.dart' ; import 'finders.dart' ; void main ( ) { group ( 'list content test' , ( ) { FlutterDriver driver ; setUpAll ( ( ) async { driver = await FlutterDriver . connect ( ) ; } ) ; tearDownAll ( ( ) async { if ( driver != null ) driver . close ( ) ; } ) ; test ( 'Verify empty list message is shown' , ( ) async { await driver . waitFor ( emptyMessage ) ; } ) ; test ( 'Tap show stores button, verify stores shown' , ( ) async { await driver . tap ( showStores ) ; await driver . waitFor ( firstStore ) ; await driver . waitForAbsent ( firstProduct ) ; await driver . waitForAbsent ( emptyMessage ) ; } ) ; test ( 'Tap show products button, verify products shown' , ( ) async { await driver . tap ( showProducts ) ; await driver . waitFor ( firstProduct ) ; await driver . waitForAbsent ( firstStore ) ; await driver . waitForAbsent ( emptyMessage ) ; } ) ; } ) ; }

Running the testS

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

Run the test flutter drive --target=test_driver/list_content.dart 1 flutter drive -- target = test_driver / list_content . dart

The output should look something like this.

tEST OUTPUT Using device HTC One M8. Starting application: test_driver/list_content.dart Initializing gradle... 0.4s Resolving dependencies... 0.6s Installing build/app/outputs/apk/app.apk... 8.0s Running 'gradlew assembleDebug'... 1.1s Built build/app/outputs/apk/app-debug.apk (21.5MB). I/flutter ( 8102): Diagnostic server listening on http://127.0.0.1:49063/ I/flutter ( 8102): Observatory listening on http://127.0.0.1:52393/ 00:00 +0: list content test (setUpAll) [info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:8114/ [trace] FlutterDriver: Looking for the isolate [trace] FlutterDriver: Isolate is paused at start. [trace] FlutterDriver: Attempting to resume isolate [trace] FlutterDriver: Waiting for service extension [info ] FlutterDriver: Connected to Flutter application. 00:02 +0: list content test Verify empty list message is shown 00:02 +1: list content test Tap show stores button, verify stores shown 00:03 +2: list content test Tap show products button, verify products shown 00:04 +3: list content test (tearDownAll) 00:04 +3: All tests passed! Stopping application instance. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Using device HTC One M8 . Starting application : test_driver / list_content . dart Initializing gradle . . . 0.4s Resolving dependencies . . . 0.6s Installing build / app / outputs / apk / app . apk . . . 8.0s Running 'gradlew assembleDebug' . . . 1.1s Built build / app / outputs / apk / app - debug . apk ( 21.5MB ) . I / flutter ( 8102 ) : Diagnostic server listening on http : //127.0.0.1:49063/ I / flutter ( 8102 ) : Observatory listening on http : //127.0.0.1:52393/ 00 : 00 + 0 : list content test ( setUpAll ) [ info ] FlutterDriver : Connecting to Flutter application at http : //127.0.0.1:8114/ [ trace ] FlutterDriver : Looking for the isolate [ trace ] FlutterDriver : Isolate is paused at start . [ trace ] FlutterDriver : Attempting to resume isolate [ trace ] FlutterDriver : Waiting for service extension [ info ] FlutterDriver : Connected to Flutter application . 00 : 02 + 0 : list content test Verify empty list message is shown 00 : 02 + 1 : list content test Tap show stores button , verify stores shown 00 : 03 + 2 : list content test Tap show products button , verify products shown 00 : 04 + 3 : list content test ( tearDownAll ) 00 : 04 + 3 : All tests passed ! Stopping application instance .

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!).

Related