integration_test_helper 0.1.0 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.
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