integration_test_helper 0.1.0 copy "integration_test_helper: ^0.1.0" to clipboard
integration_test_helper: ^0.1.0 copied to clipboard

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 package has pre-configured methods that allow for faster test deployment for end to end (e2e) test coverage (using Android and iOS platform UIs). It also allows for dynamic / non-hard coded UX integration tests, using the BaseIntegrationTest.loadFixtureJSON helper method, where you can load JSON data from your project's assets directory.

In addition, it is the core of the integration_test_preview package, which also allows structured Flutter integration tests, but includes a generated directory of screenshots for each screen type available from the device_preview package (as well as custom screen types).

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.4
    
dev_dependencies:

  flutter_test:
    sdk: flutter

  integration_test:
    sdk: flutter

  integration_test_helper: <latest_version>

Add Integration Test Driver file (test_driver/app_features_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_feature_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_features.dart) #


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

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

void main() async {

    IntegrationTestHelperBinding.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_features.dart) #


    flutter drive -t integration_test/app_features.dart

Additional information #

Alternatively, you can run the example #

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

Package Support #

To contribute to this repo, take a look at the CONTRIBUTE.md file.

Package Documentation #

To view the documentation on the package, follow this link

9
likes
120
pub points
51%
popularity

Publisher

unverified uploader

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

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (LICENSE)

Dependencies

flutter, flutter_test, integration_test, intl

More

Packages that depend on integration_test_helper