Unit and widget tests in flutter help us test classes, functions, methods and widgets, but they don't help for larger test cases, like performance, and communication between screens, this is where integration tests come to play.
This is a follow-up post to the previous one: Flutter Automated Tests — Getting Started where I wrote about setting up tests in flutter apps and creating unit and widget tests, you should check it out!
What we need
- I'll continue using the example app in the previous article.
- flutter_driver package for creating test pairs (we'll get to this in a second).
Set up
The example app is a simple page with a text field what you type get's printed on the screen:
TextEditingController input = TextEditingController();
String text = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Let's Test This!"),
),
body: Center(
child: Container(
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 250,
child: TextField(
key: Key('textfield'),
controller: input,
),
),
RaisedButton(
child: Text('Done'),
onPressed: () {
setState(() {
text = input.text;
});
},
color: Colors.blue,
textColor: Colors.white,
)
],
),
SizedBox(
height: 70,
),
Text(
text,
style: TextStyle(fontSize: 20),
),
],
),
),
),
);
}
Next, we set up dependencies.
For Unit and widget tests, we used tools in the flutter_test package, provided by flutter on the fly. However, for integration tests, we use the flutter_driver package, so go on and add it to dev_dependencies in your pubspec.yaml file:
dev_dependencies:
flutter_driver:
sdk: flutter
test: any
Also add the dart test
package if you haven't already, this is because we would be using some tools in it such as find
, test
, group
, etc.
Write some tests
Before we get into writing the tests, there's something we need to add to our page's code - value Keys to the textfield and done button. We need to add these keys so we can find
those widgets and test them (similar to what we did in widget tests).
Go ahead and add key: Key('done')
to the RaisedButton
like so:
RaisedButton(
child: Text('Done'),
key: Key('done'),
onPressed: () {
setState(() {
text = input.text;
});
},
color: Colors.blue,
textColor: Colors.white,
),
SizedBox(
height: 70,
),
Text(
text,
key: Key('output'),
style: TextStyle(
fontSize: 20,
),
)
Write Tests!
Integration tests are different from unit and widget tests because they run in a separate process from the main app itself. so there are a few extra steps flutter needs us to do:
- Create a folder to store the needed files, I'll name mine integration_tests .
- Next, create a file that contains what flutter terms an instrumented version of the app. It is what helps you run the app in a test environment. I'll name mine testApp.dart.
- Then, we'd also need to create a file where we write the actual tests, in the same folder. But make sure it's in this format, instrumentedApp_test, the _test added to the name of the instrumented app.
- Finally, we get to writing the code.
In the instrumented app file (testApp.dart), this is what we've got:
import 'package:flutter_driver/driver_extension.dart';
import 'package:package_name/main.dart' as app;
void main() {
enableFlutterDriverExtension();
app.main();
}
what happens here?
- We first import the
driver_extension.dart
from theflutter_driver
package we depended on previously, and the main app file of the app we want to test.- Next, we enable the Driver extension with
enableFlutterDriverExtension()
.- Call the app's
main()
function.
In the test file, our code looks like so:
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
group('Test Input and output', () {
FlutterDriver driver;
// Finders for the widgets we want to test.
var textfield = find.byValueKey('textfield');
var button = find.byValueKey('done');
var output = find.byValueKey('output');
// Connect to Flutter driver.
setUpAll(() async {
driver = await FlutterDriver.connect();
});
// close the flutter driver after tests are completed
tearDownAll(() async {
if (driver != null) {
driver.close();
}
});
test('output is empty', () async {
// Check that no text is on the screen.
expect(await driver.getText(output), "");
});
test('input text and update ', () async {
//emulate text field tap
await driver.tap(textfield);
//emulate text input
await driver.enterText("input text");
// Then, tap the done button.
await driver.tap(button);
// finally, check that value printed is correct.
expect(await driver.getText(output), "input text");
});
});
}
what's happening here?
- First, we import
flutter_driver.dart
fromflutter_driver
, andtest.dart
from darttest
package.- Next, we create a test group. Groups help us run a series of related tests, we can name groups too, this one is called "Test input and output".
- Then, we use finders from the test package to get the widgets we want to test. Here we use
find.byValueKey()
and pass in the value of the keys we added to the widgets previously and store them in variables.- Next, Connect to Flutter Driver and Close the connection after tests are done.
- Next, we write our first test case to check that no text is on the screen, we use
expect()
to assert values.- In the next test case, we are doing three things, tap the textfield, input text, and check if it is shown on the screen.
Run the Tests
To run the tests, you need to have an emulator or simulator connected as opposed to unit and widget tests, you can also use a real device.
Next, you should run this command in the project root, which builds and runs the instrumented app on the device/emulator:
$ flutter drive --target=name_of_test_folder/instrumented_app.dart
- The command then runs the test file from the same folder.
- Finally, it closes the app after running the tests.
There you have it, You've successfully set up and run integration tests in your flutter app! 🎉
Find me on twitter if you want to talk about the goodness of flutter/dart!