Integration tests allow for tests to execute on Android and iOS. This allows us to ensure platform specific behaviour (native ably-flutter code and dependencies, ably-cocoa and ably-java) are behaving as expected.
This project has only one integration test target, main.dart.
To run all tests:
- run
flutter drive --target lib/main.dart, or - run
flutter drivewhich runs them all (one and only,main.dart). - Android Studio / VS Code: Run the
Integration Testrun configuration. It is a compound run configuration which runs bothIntegration Test DriverandIntegration Test App.
Test modules include basicTests and realtime (For the full list, TestModules in tests_config.dart). To run one module (a collection of test groups), either:
- In Android Studio:
- Modify the
Integration Test Driverrun/debug configuration by adding-m module_name_1,module_name_2inProgram Arguments.- Note: No spaces between module_names. Alternatively, specify the modules separately:
-m module_name_1 -m module_name_2
- Note: No spaces between module_names. Alternatively, specify the modules separately:
- Modify the
- Launch each application manually:
- Launch the integration test app manually
- Launch the driver and pass the modules with the
-moption:dart test_integration/test_driver/runner.dart -m rest,realtime
To run a specific test group:
- In tests_config.dart, comment out the test group key: value pairs which you don't want to run.
- Launch both integration test app and driver:
- Launch
Integration Testcompound run configuration in Android, or - Launch
Integration Test AppandIntegration Test Driverrun configurations, or - Launch each application manually:
- Launch the integration test app manually
- Launch the driver:
dart test_integration/test_driver/runner.dart
- Launch
Run the bash script in the project root directory
cd test_integration
./run_integration_tests.shcd test_integration
flutter driver test_driver/app.dartWhen
flutter drive test_driver/*is executed, flutter drive searches of pairs of files in the
directory test_driver/ with the names xxx.dart and xxx_test.dart
where xxx.dart is the app to be run on the device or emulator and
xxx_test.dart the unit-test like test that controls the app by
sending commands and receiving responses that are then evaluated with
expect(...) and similar to unit tests.
flutter drive then executes the app in the found devices (or a
device or emulator specified in additional parameters) and
the test on the host platform where flutter drive is executed.
Because it is often convenient to execute the test application like a
normal application (without flutter drive), the app implementation
is put into lib/ instead of test_driver.
To satisfy flutter drive the xxx.dart file has to exist, but all
it does is to import and calls the main() function of the app in
lib/main.dart.
It is possible to have multiple applications in lib/ and different
test_driver/xxx.dart files can call different main functions from
lib/.
flutter drive provides a set of functions to simulate user input
like touch, but here we discuss only non-UI drive tests.
The test needs to establish a connection with the application by
executing driver = await FlutterDriver.connect();.
Tests send a message to the app using
final result = await driver.requestData(message.toJson());Only String values are supported as message content, therefore we
use JSON serialized strings.
enableFlutterDriverExtension(handler: dataHandler);This code needs to be executed before runApp().
DriverDataHandler is a helper class that deserializes received
messages and serializes responses.
TestControlMessage is another helper class that ensures the message
conforms to some minimal structure. For example that a test name is
passed.
It additionally supports a Map<String, dynamic> as payload that
allows any JSON-serializable value.
TestDispatcher is a minimal application widget that invokes tests
depending on the name in the received message.
A mapping from test name to a test widget factory needs to be passed
to lib.main(...).
Example:
final testFactory = <String, TestFactory>{
TestName.platformAndAblyVersion: (d) => PlatformAndAblyVersionTest(d),
TestName.appKeyProvisioning: (d) => AppKeyProvisionTest(d),
};
void main() => app.main(testFactory);When test code in the app is done, it needs to call completeTest()
with the response data to notify the test about the result.
The response data again needs to be a JSON-serializable Map<String, dynamic>.
Exceptions need to be handled by the test code in the app and
communicated to the test using the completeTest() method.
If an app contains multiple tests, they will have some dependency between them, because the app is not restarted for each test which results for example in the plugin to keep the state from the previous test.
To have truly independent tests, each test needs to have its own
xxx.dart and xxx_test.dart file pair in the test_driver/
directory.
They can all call the same lib/main.dart though, because
for each xxx_test.dart file the app will be closed and restarted.
Currently sending a message from the driver test to the app starts a new test and completing a test sends a response.
It would be possible to support multiple messages per test in both directions, but it is not yet clear if this is actually required.
This is an attempt to have a clean structure in apps with multiple tests. Time should tell if this is a good approach.
See https://medium.com/flutter-community/hot-reload-for-flutter-integration-tests-e0478b63bd54 for how to improve development performance by utilizing Hot Reload/Restart for Driver tests