If you haven't already read it, start with the repository structure overview to familiarize yourself with the types of packages discussed below.
Because of the complexities of having native code, plugins have many more types of tests than most other Flutter packages. A plugin will generally have some combination of the following:
Dart unit tests. All plugin packages should have these, with the exception of a federated implementation package that contains only native code (i.e., one that only implements a shared method channel). In a plugin, Dart unit tests would cover:
These should live in test/
Integration tests. These use the integration_test
package. Unlike Dart unit tests, integration tests run in the context of a Flutter application (the example/
app), and can therefore load and exercise native plugin code. Almost all plugin packages other than the platform interface package should have these. The only exceptions would be:
These should live in example/integration_tests/
Native unit tests. Most plugins that have native code should have unit tests for that native code. (Currently, many do not; fixing that is currently a priority for plugins work.) They are written as:
android/src/test/
example/ios/RunnerTests/
, or darwin/RunnerTests/
for a plugin with shared macOS source (These are in the example directory for single-platform projects because they are run via the example app's project)example/macos/RunnerTests/
, or darwin/RunnerTests/
for a plugin with shared iOS source (These are in the example directory for single-platform projects because they are run via the example app's project)linux/test/
, and be named *_test.cc
.windows/test/
, and be named *_test.cpp
.Native UI tests. Some plugins show native UI that the test must interact with (e.g., image_picker
). For these normal integration tests won't work, as there is not way to drive the native UI from Dart. They are written as:
espresso
plugin - These should live in example/android/app/src/androidTest/
example/ios/RunnerUITests/
, or darwin/RunnerUITests/
for a plugin with shared macOS sourceexample/macos/RunnerUITests/
, or darwin/RunnerUITests/
for a plugin with shared iOS sourceThese can be run like any other Flutter unit tests, either from your preferred Flutter IDE, or using flutter test
. The one exception is unit tests for *_web
packages, which must be run with flutter test --platform=chrome
.
You can also run them via the repository tools drive-examples
command:
dart run script/tool/bin/flutter_plugin_tools.dart drive-examples --packages=<name_of_plugin>
To run the integration tests using Flutter test (while in a plugin directory):
cd example
flutter test integration_test
(optionally including a -d <device>
flag to select the target device), or use the repository tools drive-examples
command from the root of the repository:
dart run script/tool/bin/flutter_plugin_tools.dart drive-examples --packages=<name_of_plugin> --<platform>
To run integration tests as instrumentation tests on a local Android device:
cd example flutter build apk cd android && ./gradlew -Ptarget=$(pwd)/../test_driver/<name_of_plugin>_test.dart app:connectedAndroidTest
From a terminal (while at the repository root), use the repository tools native-test
command. Examples:
# Unit and integration (UI) tests, multiple platforms dart run script/tool/bin/flutter_plugin_tools.dart native-test --android --ios --packages=<some_plugin_name> # iOS, integration tests only dart run script/tool/bin/flutter_plugin_tools.dart native-test --ios --no-unit --packages=<some_plugin_name> # Android, unit tests only dart run script/tool/bin/flutter_plugin_tools.dart native-test --android --no-integration --packages=<some_plugin_name>
It's also possible to run them from native IDEs:
Run from Android Studio once the example app is opened as an Android project
Run from Xcode once the example app is opened as an Xcode project.
For Windows plugins, Visual Studio should auto-detect the tests and allow running them as usual.
Most web tests are written as Integration Tests because they need a web browser (like Chrome) to run. Web integration tests are located in the example
directory of the <plugin_name_web>
package.
To run web integration tests:
chromedriver --port=4444
plugins
directory, run:dart run script/tool/bin/flutter_plugin_tools.dart drive-examples --packages=<plugin_name>/<plugin_name_web> --web
cd
into the example
directory of the package, then run:flutter drive -d web-server --web-port 7357 --browser-name chrome --driver test_driver/integration_test.dart --target integration_test/NAME_OF_YOUR_test.dart
All web packages contain a standard test
directory in the root of the package that can be run with flutter test
. In most cases, the test there will direct users to the Integration Tests that live in the example
directory. In some cases (like file_selector_web
), the directory contains actual Unit Tests that are not web-specific and can run in the Dart VM.
Some packages (like google_maps_flutter_web
) contain .mocks.dart
files alongside the test files that use them.
Mock files are generated by package:mockito
. The contents of mock files can change with how the mocks are used within the tests, in addition to actual changes in the APIs they're mocking.
Mock files must be updated by running the following command:
flutter pub run build_runner build
A PR changing a plugin should add tests. Which type(s) of tests you should add will depend on what exactly you are changing. See below for some high-level guidance, but if you're not sure please ask in your PR or in #hackers-ecosystem
on Discord. Hopefully the scaffolding to run the necessary kinds of tests are already in place for that plugin, in which case you just add tests to the existing files in the locations referenced above. If not, see below for more information about adding test scaffolding.
Yes. Much of the plugin code predates the current strict policy about testing, and as a result the coverage is not as complete as it should be. The goal of having a strict policy going forward is to ensure that the situation improves.
The type(s) of tests to add depends on the details of the PR, and there is not always one right answer. Some high level principles to keep in mind:
Some specific guidelines about different test types:
Some patterns to consider given all of the above:
If a plugin is missing test scaffolding for the type of tests you want to add, you have several options:
#hackers-ecosystem
.Regardless of the approach you use, please reach out on Discord if a PR that sets up a new kind of test doesn't get reviewed within two weeks. Filling in the gaps in test scaffolding is a priority for the team, so we want to review such PRs quickly whenever possible.
See below for instructions on bringing up test scaffolding in a plugin (does not yet cover all types of tests):
Open <path_to_plugin>/example/ios/Runner.xcworkspace
using Xcode. (For macOS, replace ios
with macos
.)
Create a new “Unit Testing Bundle” or “UI Testing Bundle”, depending on the type of test.
In the target options window, populate details as following, then click on “Finish”.
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER
is currently incompatible with Flutter.IPHONEOS_DEPLOYMENT_TARGET
and TARGETED_DEVICE_FAMILY
may cause issues running tests.CLANG_*
, GCC_*
, and MTL_*
) shouldn't be needed.Use of OCMock in new tests, especially Swift tests, is discouraged in favor of protocol-based programming and dependency injection. However, if your XCTests require OCMock, open the Package Dependencies section of the project in Xcode, and add the following dependency to the RunnnerTests target:
https://github.com/erikdoe/ocmock Commit fe1661a3efed11831a6452f4b1a0c5e6ddc08c3d
OCMock must be set to Commit due to its use of unsafe build flags.
A RunnerTests/RunnerUITests folder should be created and you can start hacking in the added .m
/.swift
file.
Duplicate the DartIntegrationTests.java
file from another plugin to example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java
Create a file under example/android/app/src/androidTest/java/
with a sub-path corresponding to the example app's package identifier from example/android/app/src/main/AndroidManifest.xml
. The file should be called FlutterActivityTest.java
(or if the example uses a custom MainActivity as its android:name
in AndroidManifest.xml
, MainActivityTest.java
).
AndroidManifest.xml
uses io.flutter.plugins.fooexample
as the package identifier, and io.flutter.embedding.android.FlutterActivity
as its android:name
, the file should be example/android/app/src/androidTest/java/io/flutter/plugins/fooexample/FlutterActivityTest.java
.The file should look like:
package io.flutter.plugins.fooexample; import androidx.test.rule.ActivityTestRule; import dev.flutter.plugins.integration_test.FlutterTestRunner; import io.flutter.embedding.android.FlutterActivity; import io.flutter.plugins.DartIntegrationTest; import org.junit.Rule; import org.junit.runner.RunWith; @DartIntegrationTest @RunWith(FlutterTestRunner.class) public class FlutterActivityTest { @Rule public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class); }
Note:
package
to match the actual package.MainActivity
, replace the FlutterActivity
references with MainActivity
.Ensure that example/android/app/build.gradle
's defaultConfig
section contains:
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"