Press ESC to close

Flutter App : The Integration Test Helper has pre-configured methods that allow for faster test deployment for end to end (e2e) test coverage

The Integration Test Helper has pre-configured methods that allow for faster test deployment for end to end (e2e) test coverage (using Android and iOS platform UIs).

Open Drawer

Languages

Counter

The MAC

All Pages

Features

The Integration Test Helper is built on top of Flutter’s Integration Tests. Running End to End (e2e) tests can become bloated and unorganized code, and lead to regressions but with this helper, writing tests can be faster, modular and with full test coverage. This approach allows for a cleaner development experience, and less regressions within your apps.

Regression Testing

Integration Test Helper (or the BaseIntegrationTest class) allows for BlackBox Testing using fixture data. The fixtures currently support JSON data, and can be loaded from anywhere within the project folder. Here is what the fixture test data (assets/fixtures/languages.json) looks like that is being blackbox tested…

{
    "count": 7,
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 1,
            "name": "Python",
            "year": 1991,
            "person": "Guido van Rossum",
            "favorited": true,
            "category" : "Scripting, Object Oriented",
            "logo": "logos/python.png",
            "hello" : "helloworld/1_code_prism_language_python.png",
            "arguments" : "arguments/1_code_prism_language_python.png",
            "description" : "Python is an interpreted high-level general-purpose programming language. Guido van Rossum began working on Python in the late 1980s, as a successor to the ABC programming language, and first released it in 1991 as Python 0.9.0. Python’s design philosophy emphasizes code readability with its notable use of significant indentation. Its language constructs as well as its object-oriented approach aim to help programmers write clear, logical code for small and large-scale projects."
        },
        ...
    ]
}

This data is typically initialized in the setupInitialData implementation of the BaseIntegrationTest subclass. The following is an example of how you can BlackBox Test your ListViews, as well other types of Widgets with Integration Test Helper:

class ScreenIntegrationTestGroups extends BaseIntegrationTest { late Map _languagesTestData; @override Future<void> setupInitialData() async { _languagesTestData = await loadFixtureJSON('assets/fixtures/languages.json') as Map; if (_languagesTestData.isEmpty) { throw 'No languages test data found'; } } Future<void> validateTestDataAt(int itemIndex, { required String widgetSuffix, required String jsonKey }) async { var languageData = _languagesTestData['results'][itemIndex] as Map; var itemText = languageData[jsonKey] as String; await verifyListExactText(itemIndex, widgetPrefix: 'item', widgetSuffix: widgetSuffix, expectedText: itemText); } Future<void> testLanguagesFeature() async { // VIEW LANGUAGES PAGE await showLanguagesList(); await verifyTextForKey('app-bar-text', 'Languages'); await validateTestDataAt(0, widgetSuffix: 'name', jsonKey: 'name'); await validateTestDataAt(1, widgetSuffix: 'name', jsonKey: 'name'); // VIEW LANGUAGE Python PAGE await tapListItem(widgetPrefix: 'item', itemIndex: 0); await verifyExactText('Python'); await tapBackArrow(); // VIEW LANGUAGE Java PAGE await tapListItem(widgetPrefix: 'item', itemIndex: 1); await verifyExactText('Java'); await tapBackArrow(); } Future<void> testCounterFeature() async { await showCounterSample(); await verifyTextForKey('app-bar-text', 'Counter Sample'); ... } ... }

Integration Test Helper also supports all Major Widget Interactions. When tapping Widgets, the package supports tapForKey, tapForType, tapForTooltip, tapWidget(“Containing This Text”), tapListItem and more.

With the tapListItem, we handle the waiting for the UI to load, finding the Widget, and then tapping the found Widget. In addition, we also include ListView item prefixes, and positions within the list.

Future<void> tapListItem({ required String widgetPrefix, required int itemIndex }) async { await waitForUI(); final itemFinder = find.byKey(ValueKey('${widgetPrefix}_$itemIndex')); await tester.tap(itemFinder); }

Note: Using the tapListItem implementation, we remove at the least 3 lines of code from your integration tests, and allow that functionality to be reused in your own custom implementation of the BaseIntegrationTest class.

Here is what your Widget Key implementation could look like:

Card( elevation: 1.5, child: InkWell( key: Key('item_$index'), onTap: () { Navigator.push<void>(context, MaterialPageRoute(builder: (BuildContext context) => LanguagePage(index: index, language: item))); }, child: LanguagePreview(index: index, language: item)), ), );

And here is an example of using that Key to tap the list item widget:

Future<void> testLanguagesFeature() async { // VIEW LANGUAGES PAGE ... // VIEW LANGUAGE Python PAGE await tapListItem(widgetPrefix: 'item', itemIndex: 0); await verifyExactText('Python'); await tapBackArrow(); // VIEW LANGUAGE Java PAGE ... }

Getting started

Note: this package example uses another one of our packages. It’s called the drawer_manager
package, and can be found here for more details on how it works.

Install Provider, Drawer Manager & Integration Test Helper

  flutter pub get provider
  flutter pub get drawer_manager
  flutter pub get integration_test_helper

Or install Provider, Drawer Manager & Integration Test Helper (in pubspec.yaml)

    ...
    
dependencies:
  flutter:
    sdk: flutter

    ...

  provider: 6.0.2
  drawer_manager: 0.0.3
    
dev_dependencies:

  flutter_test:
    sdk: flutter

  integration_test:
    sdk: flutter

  integration_test_helper: 0.0.1

Add Integration Test Driver file (test_driver/integration_test.dart)

import 'package:integration_test/integration_test_driver.dart'; Future<void> main() => integrationDriver();

Usage

Create hello file (lib/hello.dart)

import 'package:flutter/material.dart';

class HelloPage extends StatelessWidget {

  final int position;
  
  const HelloPage({Key? key, required this.position}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        'Hello, Flutter $position!',
        key: Key('hello-page-text-$position'),
        textAlign: TextAlign.center,
        style: const TextStyle(
            color: Color(0xff0085E0),
            fontSize: 48,
            fontWeight: FontWeight.bold
        )
      ),
    );
  }
}

Create main file (lib/main.dart)

import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:drawer_manager/drawer_manager.dart'; import 'hello.dart'; void main() { runApp(setupMainWidget()); } Widget setupMainWidget() { WidgetsFlutterBinding.ensureInitialized(); return const MyApp(); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ChangeNotifierProvider<DrawerManagerProvider>( create: (_) => DrawerManagerProvider(), child: MaterialApp( title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue), home: const MyHomePage(), )); } } class MyHomePage extends StatelessWidget { const MyHomePage({Key? key}) : super(key: key); String _getTitle(int index) { switch (index) { case 0: return 'Hello 1'; case 1: return 'Hello 2'; default: return ''; } } Widget _getTitleWidget() { return Consumer<DrawerManagerProvider>(builder: (context, dmObj, _) { return Text( _getTitle(dmObj.selection), key: const Key('app-bar-text') ); }); } @override Widget build(context) { final drawerSelections = [ const HelloPage(position: 1), const HelloPage(position: 2), ]; final manager = Provider.of<DrawerManagerProvider>(context, listen: false); return Scaffold( appBar: AppBar(title: _getTitleWidget()), body: manager.body, drawer: DrawerManager( context, drawerElements: [ const DrawerHeader( decoration: BoxDecoration(color: Colors.blue), child: Padding( padding: EdgeInsets.only(bottom: 20), child: Icon( Icons.account_circle, color: Colors.blueGrey, size: 96, ), ), ), DrawerTile( key: const Key('drawer-hello-1'), context: context, leading: const Icon(Icons.hail_rounded), title: Text(_getTitle(0)), onTap: () async { // RUN A BACKEND Hello, Flutter OPERATION }, ), DrawerTile( key: const Key('drawer-hello-2'), context: context, leading: const Icon(Icons.hail_rounded), title: Text(_getTitle(1)), onTap: () async { // RUN A BACKEND Hello, Flutter OPERATION }, ) ], tileSelections: drawerSelections, )); } }

Import Flutter Test & Integration Test Helper (in integration_test/app_test_groups.dart)

    ...
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test_helper/integration_test_helper.dart';

Subclass BaseIntegrationTest (in integration_test/app_test_groups.dart)

The Integration Test Helper can support platform specific implementations, like the showHelloFlutter
method. This method uses the Drawer for Android and accomodates the Android environment.

class ScreenIntegrationTestGroups extends BaseIntegrationTest { // ... @override Future<bool> isPlatformAndroid() async { return Future.value(true); } @override Future<void> setupInitialData() async { // ... } Future<void> showHelloFlutter({required int position}) async { print('Showing Hello, Flutter $position!'); if(Platform.isAndroid) { await tapForTooltip('Open navigation menu'); await tapForKey('drawer-hello-$position'); } await waitForUI(); } Future<void> testHelloFlutterFeature() async { await showHelloFlutter(position: 1); await verifyTextForKey('app-bar-text', 'Hello 1'); await verifyTextForKey('hello-page-text-1', 'Hello, Flutter 1!'); await showHelloFlutter(position: 2); await verifyTextForKey('app-bar-text', 'Hello 2'); await verifyTextForKey('hello-page-text-2', 'Hello, Flutter 2!'); } // ... }

Setup BaseIntegrationTest Subclass (in integration_test/app_test.dart)

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

import 'package:example/main.dart' as app;
import 'app_test_groups.dart';

void main() async {

    IntegrationTestWidgetsFlutterBinding.ensureInitialized();

    testWidgets('Testing end to end single-screen integration', (WidgetTester tester) async {
      
          final main = app.setupMainWidget();
          final integrationTestGroups = ScreenIntegrationTestGroups();
          await integrationTestGroups.initializeTests(tester, main);

          await integrationTestGroups.testHelloFlutterFeature();

      }, timeout: const Timeout(Duration(minutes: 1))
    );
    
}

Run Driver on BaseIntegrationTest Subclass (using integration_test/app_test.dart)

    flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart

Additional information

Alternatively, you can run the example

The example project has 5 screens that have grouped integration tests:

Package Support

To support this repo, take a look at the SUPPORT.md file.

Package Documentation

To view the documentation on the package, follow this link

GitHub

View Github

Footer Example