Ultimate guide of How to test in Flutter

Artem Osipov
5 min readMay 7, 2020

--

In this part, we’ll test every layer of our app and how works Widget, Unit and Integration test together.

First part of the story:
https://medium.com/@artemosipov/how-i-reinvented-redux-in-flutter-f59edcbc46f1

Source code for this project can be found here:

Before we start, take a look how our app look and behave

Widget testing in Flutter

Before start, add dev_dependencies to your pubspec.yaml file

Then, under /test/ directory in the root project folder, create a file named widget_test.dart, filename actually can be anything, but I prefer that.

Now, we must decide what we want to test at all. I want test next parameters:

  1. Error caption is shown at LoginScreen
  2. Todos are showing at TodoScreen
  3. Dismissing a Todo is working at TodoScreen
  4. Todo can be checked at TodoScreen

I recommend you to always make a list of what you are going to test before actually writing tests. You can share them with your team and even non-programmers can give a valid feedback, for example, in my list I don’t test that add screen is showing at all.

Let’s write some code for the first test:

Test Error caption is shown at LoginScreen

Let’s run it and see beautiful message that test had passed..

flutter — no-color test — machine test/widget_test.dart
Looks not as beautiful as I expected

Long story short:

Icon widgets require a Directionality widget ancestor.
...
No Material widget found.
TextField widgets require a Material widget ancestor.

Icon widget require us to use Scaffold, and TextField requires us to use MaterialApp.

Let’s solve this problem once and forever by writing a simple wrapper

And modify the test

Yes! Our first ever Flutter test had passed.

Next, test Todos are showing at TodoScreen

A short note about other matchers we have:

findsNothing
Verifies that no widgets are found.

findsWidgets
Verifies that one or more widgets are found.

findsNWidgets
Verifies that a specific number of widgets are found.

Keeping this in mind, check we have two and only two listItems at screen:

Test Dismissing a Todo is working at TodoScreen

This is a little bit trickier, I need to introduce you two new methods

await tester.drag(find.byType(Dismissible), Offset(500.0, 0.0));
//will drag an Element to desired offset

And pumpAndSettle

// Build the widget until the dismiss animation ends.await tester.pumpAndSettle();

Combine them to test method:

And, the last one: Test Todo can be checked at TodoScreen

So, how to test that checkbox is checked in flutter?

Flutter doesn’t have a Finder to match the checked or unchecked Elements, but no one can stop us from writing our own.

And, test it:

Unit testing in Flutter

Start by adding dependency test to your pubspec.yaml file

dev_dependencies:
test:
//no-version, or you'll have a lot of interesting time trying to //find to resolve deps

Second, create a file named unit_test.dart in the /test folder

Unit test a pretty straightforward, so I will show you only how to test methods which produce a Stream of events.

To run a test type: flutter test test/unit_test.dart

I encourage you to look at the library repo
https://pub.dev/packages/test

Its Readme has a very good documentation with a lot of examples.

Integration testing in Flutter

Unit tests and widget tests are handy for testing individual classes, functions, or widgets. However, they generally don’t test how individual pieces work together as a whole, or capture the performance of an application running on a real device. These tasks are performed with integration tests.
https://flutter.dev/docs/cookbook/testing/integration/introduction

Integration test consists of the following steps:

  1. Create an app to test.
  2. Add the flutter_driver dependency.
  3. Create the test files.
  4. Instrument the app.
  5. Write the integration tests.
  6. Run the integration test.

Since we already has our app written, skip to the step 2.

Add dependencies

dev_dependencies:
flutter_driver:
sdk: flutter
test: any

Create test files

  1. Create folder test_driver under the project-root folder

2. Create two files app.dart and app_test.dart

Instrument the app

In the app.dart file enable the flutter driver extensions and then run your app.

Write the integration tests.

Start by writing a template for our future tests

The very next thing to simplify writing tests is to create SerializableFinders to locate specific widgets.

They looks like this:

final usernameFinder = find.byValueKey('usernameField');
final passwordFinder = find.byValueKey('passwordField');

And the key is a key property of our widget

Before we go further, write a test scenario:

  1. App starts at login screen
  2. We enter username and password
  3. We press Login button
  4. App navigates to TodoListScreen
  5. Tapping new task shows us Add form
  6. We enter text of todo
  7. We press Add button
  8. Todo is added

To check if we are at the specific screen, we can just check that specific item for this screen is present right now:

test('We start at login screen', () async {
expect(await driver.getText(loginButtonFinder), 'Login');
});

We got a text of LoginButton and check its title.

To enter a text in the TextFormField we need to do two actions:

  1. Tap it, so the field receives focus
  2. Enter text
await driver.tap(usernameFinder);
await driver.enterText('1234');

To press a button, just call tap method on it

Now, we have to wait for a second screen to load, since I use fake call to network, I know that Login operation will take 500ms.

await driver.waitFor(addFabFinder, timeout: Duration(milliseconds: 1000));

I just waiting 1s for my Fab button to show on the screen, if it’s not present after timeout test will fail.

Add new Todo title, press Add and Check that new Todo is present

expect(await driver.getText(find.byValueKey('T_$title')), title);

The last part, we need to run it on a device:

flutter drive --target=test_driver/app.dart
Whoa, that was fast!

Conclusion

We test every part of our app in every possible way.
I hope now you get an idea of how testing in Flutter work and that it’s now scary at all if your app architecture was planned to do it.

--

--