Widget tests
Last modified on Thu 13 Oct 2022

Widget tests are used to test if some widgets are present in the widget tree or to test interactions with widgets. To get familiar with it you must first learn what is a WidgetTester and Finder.

WidgetTester

While testing widgets you will use testWidgets function which gives you WidgetTester. WidgetTester is a class that programmatically interacts with widgets and the test environment. First thing you need to do in a widget test is use tester.pumpWidget(Widget) to build and render your widget. Be careful that your widgets have MaterialApp above them for the test to work properly. If you are using riverpod in your widget test make sure that also ProviderScope is a parent widget. Many of WidgetTester methods are async and it is important that you await them. Widgets may call setState and they will not rebuild until you tell it to. To rebuild a single frame call tester.pump() or if you have an animation with many frames to rebuild you can call tester.pumpAndSettle() which will repeatedly call pump() until there are no longer any rebuilds scheduled.

Finder

Before interacting with widgets you have to find them in the widget tree. For that you'll use Finder class. Widgets can be found by type, key, text or many more. You can check out all available methods here. It is important that you realise that all of these methods return Finder class and not Widget. If you need widget instance from the widget tree you can use tester.widget(Finder) to get it like this:

final button = tester.widget<MaterialButton>(find.byType(MaterialButton));
expect(button.enabled, true);

You can use finders to check if some widgets/text are present for example:

expect(find.text('non sense'), findsNothing);

expect(find.byType(MaterialButton), findsOneWidget);

expect(find.byKey(const Key('my_key')), findsNWidgets(4));

Notice different matchers you can use 'findsNothing', 'findsOneWidget', 'findsNWidgets(int)' or to just check if there are widgets you can use 'findsWidgets'.

Interactions

Now that you can build and find your widgets let's interact with them. You can (single/double/triple) tap on widgets, long press, drag widgets on the screen, enter text into fields... In most cases these will be enough. Many of these methods accept finder as argument so that they know what is their target. Methods for interacting with widgets:

await tester.tap(Finder);
await tester.longPress(Finder);
await tester.drag(Finder, Offset);
await tester.enterText(Finder, String);

Full widget test example:

testWidgets('Test remove a todo', (tester) async {
  // Build the widget
  await tester.pumpWidget(const TodoList());

  // extract finders for easier re-use
  final todoFinder = find.byType(Dismissible);

  // Drag ToDo off screen to dismiss it
  await tester.drag(todoFinder, const Offset(500.0, 0.0));

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

  expect(todoFinder, findsNothing);
});