Flute — Flutter Integration Testing

John Blanchard
3 min readAug 28, 2019

--

It’s great to write tests. But I think developers (myself included) are confused by the reward behind test integrations. In practice it is easy to write test syntax, today Xcode will launch a simulator and generate code from a guided app session. With the generated code as a friendly route, iOS developers can easily add assertions to the session.

When it comes to writing tests, semantics are more important than syntax. However, often I find myself losing the motivation behind making my code unit-testable. That motivation is to save time, I want to know if some change to the code-base will break ‘working’ features. With every assertion, there has to be a strong assumption that implies the apps’ health. Not all tests are created equally.

At first glance there are a few different types of Flutter tests. The one I focused on, because it saves the most time, are integration tests. Unlike widget and unit tests, integration tests use driver to interact with your app. Because of this, more parts of the app can be encompassed. Simulating a tap on the login-button may have multiple side-effects that are prominently testable. Where calling some login routine in space, may provide insights into the user model, an integration test will provide insights into the app’s behavior.

Eventually I got tired of copying the integration test recipe from Flutter for every app. So I wrote an npm package, it sets-up the project and has a shortcut for driving tests.

Prior to using the package. It is necessary to install Flutter and create some app.

flutter create new-app

Be sure your main.dart can be reached from the lib directory, and that pubspec.yaml exists at the root directory. After, download flute from npm.

npm -i flute-test

Next setup tests.

cd new-app
flute setup

At the root of new-app will exist one new directory called test_driver. Inside test_driver open up app_test.dart, edit this file for your testing needs. A short example scaffold exists inside this file now.

import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {

FlutterDriver driver;

setUpAll(() async => driver = await FlutterDriver.connect());

tearDownAll(() async => driver?.close());

group('Welcome:', () {

test('Open Menu', () async {
// This is an example that uses a widget key to figure out if the menu has opened.
//final navLabel = find.byValueKey('leadingNav');
//final navText = await driver.getText(navLabel);
//expect(navText, 'Menu');
});

});

}

The main function is used by Flutter to symbolize tests and provide readable output. Most of the code inside test_driver/app_test is boilerplate. The driver is a property that handles moving throughout the app. The two functions below driver are called prior to any tests and after all tests. One function will connect to the simulator/emulator, while the other cleans up afterwards.

A group is a set of related tests. Using groups will make for a more readable output, however it’s important to note that tests are executed sequentially regardless of their group. If group A has ten tests, group B will use the state of the app at the end of ten A tests. Group C starts with the state of the app after group A and B are finished.

To verbosely describe tests, the dart team offers a package. To test assertions, use expect. The expect function takes anything and uses Matcher to imply correctness.

After writing some primitive tests, I like to add new features using the driver. After adding a new navigation button to the top, add a tap action to the driver and execute flute’s drive.

flute drive

The simulator will kick up a new session that clicks through the new navigation button. Now one can use drive to develop the next page. This is a good thing because hot-reload does not hold state. When developing full-app features, the drive command ensures consistent results because the entire app is re-run across a known route.

It is possible to also inject state into your tests. Open up test_driver/app.dart and call app.main() with some state. This requires altering the main function of your app to take some parameter.

Here’s the flute-test package repo. It’s all open, and I welcome contributions. Thank you.

--

--