Unit tests and Widget tests are handy for testing individual classes, functions,or Widgets. However, they generally don’t test how individual pieces worktogether as a whole or capture the performance of an application running on areal device. These tasks are performed with integration tests.

Integration tests work as a pair: first, deploy an instrumented application to areal device or emulator and then “drive” the application from a separate testsuite, checking to make sure everything is correct along the way.

To create this test pair, we can use theflutter_driverpackage. It provides tools to create instrumented apps and drive those appsfrom a test suite.

In this recipe, we’ll learn how to test a counter app. It will demonstratehow to setup integration tests, how to verify specific text is displayed by theapp, how to tap on specific Widgets, and how to run integration tests.

Directions

  • Create an app to test
  • Add the flutter_driver dependency
  • Create the test files
  • Instrument the app
  • Write the integration tests
  • Run the integration test

1. Create an app to test

First, we’ll create an app that we can test! In this example, we’ll test thecounter app produced by the flutter create command. This app allowsa user to tap on a button to increase a counter.

Furthermore, we’ll also need to provide aValueKey tothe Text and FloatingActionButton Widgets. This allows us to identifyand interact with these specific Widgets inside the test suite.

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(MyApp());
  3. class MyApp extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. title: 'Counter App',
  8. home: MyHomePage(title: 'Counter App Home Page'),
  9. );
  10. }
  11. }
  12. class MyHomePage extends StatefulWidget {
  13. MyHomePage({Key key, this.title}) : super(key: key);
  14. final String title;
  15. @override
  16. _MyHomePageState createState() => _MyHomePageState();
  17. }
  18. class _MyHomePageState extends State<MyHomePage> {
  19. int _counter = 0;
  20. void _incrementCounter() {
  21. setState(() {
  22. _counter++;
  23. });
  24. }
  25. @override
  26. Widget build(BuildContext context) {
  27. return Scaffold(
  28. appBar: AppBar(
  29. title: Text(widget.title),
  30. ),
  31. body: Center(
  32. child: Column(
  33. mainAxisAlignment: MainAxisAlignment.center,
  34. children: <Widget>[
  35. Text(
  36. 'You have pushed the button this many times:',
  37. ),
  38. Text(
  39. '$_counter',
  40. // Provide a Key to this specific Text Widget. This allows us
  41. // to identify this specific Widget from inside our test suite and
  42. // read the text.
  43. key: Key('counter'),
  44. style: Theme.of(context).textTheme.display1,
  45. ),
  46. ],
  47. ),
  48. ),
  49. floatingActionButton: FloatingActionButton(
  50. // Provide a Key to this the button. This allows us to find this
  51. // specific button and tap it inside the test suite.
  52. key: Key('increment'),
  53. onPressed: _incrementCounter,
  54. tooltip: 'Increment',
  55. child: Icon(Icons.add),
  56. ),
  57. );
  58. }
  59. }

2. Add the flutter_driver dependency

Next, we’ll need the flutter_driver package to write integration tests. Wecan add the flutter_driver dependency to the dev_dependencies section ofour apps’s pubspec.yaml file.

We also add the test dependency in order to use actual test functions andassertions.

  1. dev_dependencies:
  2. flutter_driver:
  3. sdk: flutter
  4. test: any

3. Create the test files

Unlike unit and widget tests, integration test suites do not run in the sameprocess as the app being tested. Therefore, we need to create two files thatreside in the same directory. By convention, the directory is namedtest_driver.

  • The first file contains an “instrumented” version of the app. The instrumentation allows us to “drive” the app and record performance profiles from a test suite. This file can be given any name that makes sense. For this example, create a file called test_driver/app.dart.
  • The second file contains the test suite, which drives the app and verifies it works as expected. The test suite can also record performance profiles. The name of the test file must correspond to the name of the file that contains the instrumented app, with _test added at the end. Therefore, create a second file called test_driver/app_test.dart.This leaves us with the following directory structure:
  1. counter_app/
  2. lib/
  3. main.dart
  4. test_driver/
  5. app.dart
  6. app_test.dart

4. Instrument the app

Now, we can instrument the app. This will involve two steps:

  • Enable the flutter driver extensions
  • Run the appWe will add this code inside the test_driver/app.dart file.
  1. import 'package:flutter_driver/driver_extension.dart';
  2. import 'package:counter_app/main.dart' as app;
  3. void main() {
  4. // This line enables the extension
  5. enableFlutterDriverExtension();
  6. // Call the `main()` function of your app or call `runApp` with any widget you
  7. // are interested in testing.
  8. app.main();
  9. }

5. Write the tests

Now that we have an instrumented app, we can write tests for it! Thiswill involve four steps:

  • Create SeralizableFinders to locate specific Widgets
  • Connect to the app before our tests run in the setUpAll function
  • Test the important scenarios
  • Disconnect from the app in the teardownAll function after our tests complete
  1. // Imports the Flutter Driver API
  2. import 'package:flutter_driver/flutter_driver.dart';
  3. import 'package:test/test.dart';
  4. void main() {
  5. group('Counter App', () {
  6. // First, define the Finders. We can use these to locate Widgets from the
  7. // test suite. Note: the Strings provided to the `byValueKey` method must
  8. // be the same as the Strings we used for the Keys in step 1.
  9. final counterTextFinder = find.byValueKey('counter');
  10. final buttonFinder = find.byValueKey('increment');
  11. FlutterDriver driver;
  12. // Connect to the Flutter driver before running any tests
  13. setUpAll(() async {
  14. driver = await FlutterDriver.connect();
  15. });
  16. // Close the connection to the driver after the tests have completed
  17. tearDownAll(() async {
  18. if (driver != null) {
  19. driver.close();
  20. }
  21. });
  22. test('starts at 0', () async {
  23. // Use the `driver.getText` method to verify the counter starts at 0.
  24. expect(await driver.getText(counterTextFinder), "0");
  25. });
  26. test('increments the counter', () async {
  27. // First, tap on the button
  28. await driver.tap(buttonFinder);
  29. // Then, verify the counter text has been incremented by 1
  30. expect(await driver.getText(counterTextFinder), "1");
  31. });
  32. });
  33. }

6. Run the tests

Now that we have an instrumented app and a test suite, we can run the tests!First, be sure to launch an Android Emulator, iOS Simulator, or connect yourcomputer to a real iOS / Android device.

Then, run the following command from the root of the project:

  1. flutter drive --target=test_driver/app.dart

This command:

  • builds the —target app and installs it on the emulator / device
  • launches the app
  • runs the app_test.dart test suite located in test_driver/ folder