Testing
Last modified on Wed 13 Apr 2022

Writing tests is important. If done right, this will improve the development experience of everyone working with the codebase, from us to PR reviewers to the one maintaining the app years later.

Besides checking that a piece of code works, testing provides additional benefits. If we write the tests first, they assist us in development. While refactoring or implementing new features, they serve as a guard, notifying us if we unintentionally break something. Finally, they describe what a piece of code does, and thus serve as part of the documentation.

Organizing tests

The folder structure of unit and widget tests should be the same as the folder structure of production code. The name of the test file should be the same as the name of the production file, with the _test suffix added (lib/common/string_util.dart -> test/common/string_util_test.dart).

The integration tests should be placed in a separate folder in the root of the app (integration_test/login_test.dart).

Testing

Use the flutter_test library to test Flutter apps. Take a look at the official Flutter testing Cookbook, it provides an excelent introduction into writing tests.

With unit tests, we test small, isolated parts of the app. These include tests for individual methods and classes. Unit tests are the easiest to write and usually represent the majority of all tests.

Use widget tests to test the appearance and behavior of widgets. These tests are easiest to write for StatelessWidgets.

With integration tests, we test how parts of the app work together as a whole. Integration tests run on a real device. They are usually the hardest to write and take the most time to execute. If you do write them, test the important scenarios.

Some piece of code might rely (have a dependency) on another piece of code. If we only want to test the piece one, we need to create a mock for the peace two. This way we have full control of the dependent pieces, and we can verify how the pieces interact. Use the mockito library to mock dependencies.

To avoid widget regression, we sometimes write golden tests. They help verify that refactoring didn't break the widget appearance. Golden tests work by first creating an image of the widget. After refactoring, the image gets re-created and compared to the original one. We use the golden_toolkit library to make writing golden tests easier.

Testing code that uses riverpod, can be done as part of widget or pure Dart tests. Making providers work correctly and overriding them with mocks and stubs requires a bit of setup. Take a look at the riverpod testing documentation to learn more.