3 Levels of Flutter Tests

Moon
5 min readMar 10, 2023

--

When to use unit/widget/integration test

Original codelab instructions. My implementation on GitHub.

Learning Objectives

  1. Unit test for a single piece of the software. An example here is the icon onPressed function (packages: test, flutter_test )
  2. Widget test for one screen. (same packages, but testWidgets class and WidgetTester class)
  3. Integration test for entire UI and app performance (packages: integration_test, flutter_driver)

Overall, the unit test designs the test from the perspective of the code; the widget test designs the test from the perspective of possible interactions with a single widget; Integration tests always run on a specific target device as a whole.

Self-driving instrumentation test on the target device!

Step 1: Set Up The App

The example app lists items you can add or remove from your favorite list.

Favorites model: A ChangeNotifier (items model) for the favorite items. It maintains a list of items using add() and remove().

Favorites screen: UI for the favorite items. It is a Scaffold (a screen) that uses Consumer to update ListView.builder() made of stateless ListTile (an item). The trailing of each ListTile has an IconButton that lets you remove() this item using Provider and ScaffoldMessenger (as the name implies, it is an InheritedWidget sits on top of Scaffold in the tree so that your SnackBar can live through screens). This makes sense because, to keep this app simple, the only action you want to take on an item that’s already in the favorite list is to remove it. You can’t edit or duplicate. Key is used to make the items updated correctly (due to how the element tree identifies the changed widget tree, well explained here)

Items screen: UI for all items. Similar to the favorite screen. This is a more generalized case in the sense it has one more action than the the favorite screen besides remove(): add(). It again uses ListView and ListTile. Another difference is now the icon has two states to indicate whether this icon is added to the favorite list. If it’s added, the icon should be Icons.favorite. Otherwise, it’s Icons.favorite_border. You can enter the favorite screen from this item screen through the icon in appBar. That’s all the difference!

Main: It handles the routing and theme for the entire app. A router is created using GoRouter with paths defined by the two screens created above. The state of this router is managed by ChangeNotifierProvider as a MaterialApp.router child.

Step 2: Unit Test

By convention, the directory structure in the test directory mimics that in the lib directory, and the Dart files have the same name with _test appended.

  1. Each unit test() takes in two arguments, a description and a callback. Inside each test, we call the functions to be tested (add() and remove() here), then set expect().
  2. We can organize similar tests into a group() the flutter testing framework provides.
  3. You should see something like 00:01 +2: All tests passed! when cli flutter test test/models/favorites_test.dart or flutter test

Step 3: Widget Test

  1. Similar to unit test, widget test takes in the same two arguments, a description, and a callback. However, the difference (1) we use testWidget() instead of test() (2) the callback takes in a WidgetTester as its argument to simulate the user behavior, which is stricter but more advanced than test().
  2. Instead of testing the function directly as we did in a unit test, we first need to load this widget in the widget test. We use tester.pumpWidget() to load this widget into testWidgets(). tester is the WidgetTester type as mentioned above. Notice that we don’t just load this widget itself. Instead, we need first to wrap it in ChangeNotifierProvider() and MaterialApp() because our widget needs to get some data from them by inheriting them in the tree.
  3. Test if ListView rendered: we load the widget, find.byType(ListView) , and expect() to findsOneWidget.
  4. Test Scrolling: For the actual interactions, we use fling() and pumpAndSettle() to simulate the fling gesture and remove all frames.
  5. Test icons: similar to the above test, we load (pumpWidget) => expect (byIcon) => interact (tap) => expect(text) again, or any user interaction path you can think of
Touch events slide from Jones & Bartlett Learning: https://slideplayer.com/slide/10650689/

Step 4: UI Integration Test

The integration test doesn’t follow the unit test file structure convention. It loads in the entire app rather than a single widget to test.

The integration_test library is used to perform integration tests in Flutter. This is Flutter's version of Selenium WebDriver, Protractor, Espresso, or Earl Gray. The package uses flutter_driver internally to drive the test on a device.

Since we use ListView, we need to provide a key to uniquely identify a specific widget. We test adding multiple items to the favorite list and then removing multiple items from the favorite list. This is directly done on the entire App() rather than a createHomeScreen() as the last testing level.

Step 5: App Performance Test

Performance test also loads the entire app. Hence it’s also an integration test. However, it has extra steps the UI integration test doesn’t have. integration_test package enables self-driving testing of Flutter code on devices and emulators and adapts flutter_test results into a format compatible with flutter drive and native Android instrumentation testing.

Note integration_test 1.0.2+3 (previously e2e) is deprecated because it’s moved into Flutter SDK, so rather than installing it yourself, you should find it in your pubspec.yaml when you flutter create my_app .

dev_dependencies:
integration_test:
sdk: flutter

You can use FlutterDriver to write the script and run the test. For example, you can take a screenshot of the UI using await binding.takeScreenshot(‘screenshot-1’) then save it in integrationDriver(onScreenshot)

Now, run the test with

flutter drive \
--driver=test_driver/perf_driver.dart \
--target=integration_test/perf_test.dart \
--profile \
--no-dds

And watch the phone plays with itself.

Isn’t that cool!!!

If you like this tutorial, you may also find my writing on how to build A Voice Bot Mobile App helpful. They are part of my Become Flutter Comfortable in 23 Days series

--

--

Moon

Digging@PinkRain | Biologist & AI Engineer For Entrepreneurs | www.skool.com/ai-creators | Let me know when you figure out what I am doing