I'm trying to write a widget test to ensure an AlertDialog
is shown to the user.
I've got a small example to reproduce this. When manually used by a user, the widget shows the AlertDialog, but it fails to show in a widget test.
I've tried a few things:
Using different methods to retrieve the button: find.byKey
, find.byIcon
,
Using different methods to press the button: tester.tap
, tester.press
Using some arbitrary delay after pressing the button: await tester.pumpAndSettle(const Duration(milliseconds: 1000));
Checking different expected elements: expect(find.byElementType(AlertDialog), findsOneWidget);
, putting a distinct icon e.g. pokeball and getting it: expect(find.byIcon(Icons.catching_pokemon), findsOneWidget)
Setting useDialog
's useRootNavigator:false
tip: if you run flutter run main.dart
, it will run test visually on the device screen
tip 2: you can run the app by commenting out some code (see main
function)
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
const buttonKey = Key("button");
const alertDialogKey = Key("alertDialog");
class MyApp extends StatelessWidget {
showAppDialog(BuildContext context) async {
print("Showing app dialog");
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
key: alertDialogKey,
title: const Text(
"You can see this dialog, but you can't catch it with WidgetTester.",
),
icon: const Icon(Icons.catching_pokemon),
actions: [
TextButton(
onPressed: () {
// Navigator.of(context).pop();
},
child: const Text("Oops"),
),
],
);
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dialog',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: SafeArea(child: Builder(builder: (context) {
return TextButton(
key: buttonKey,
child: const Text("Show dialog"),
onPressed: () async => await showAppDialog(context),
);
}))),
);
}
}
void mainTest() {
testWidgets(
"When button is pressed, dialog is shown.",
(tester) async {
final widget = MyApp();
await tester.pumpWidget(widget);
final button = find.byKey(buttonKey);
expect(button, findsOneWidget);
await tester.press(button);
await tester.pumpAndSettle();
// None of these work:
expect(find.byKey(alertDialogKey), findsOneWidget);
expect(find.byIcon(Icons.catching_pokemon), findsOneWidget);
expect(find.byElementType(AlertDialog), findsOneWidget);
},
);
}
void main() {
// Uncomment to run the app manually
// runApp(MyApp());
// Comment out to run the app manually.
mainTest();
}
I needed to do 2 things:
await tester.runAsync(() async {})
because showDialog
is an async function. By default, Flutter doesn't actually run asynchronous work in tests, for performance reasons. 🤓tester.tap
instead of tester.press
because press
doesn't actually release button, so .press
doesn't trigger onPressed
callback 😈.void mainTest() {
testWidgets(
"When button is pressed, dialog is shown.",
(tester) async {
final widget = MyApp();
await tester.pumpWidget(widget);
final button = find.byKey(buttonKey);
expect(button, findsOneWidget);
await tester.runAsync(() async {
await tester.tap(button);
// Or alternatively press then "up":
// final response = await tester.press(button);
// await response.up();
});
await tester.pumpAndSettle();
// These all work now
expect(find.byKey(alertDialogKey), findsOneWidget);
expect(find.byIcon(Icons.catching_pokemon), findsOneWidget);
expect(find.byType(AlertDialog), findsOneWidget);
},
);
}
Extra tip: run your tests on a device to visualise what's happening. Run flutter run test/name_of_test.dart
. This helps you see where the problem is: for example, is it the AlertDialog
not showing, or the find.byType
not finding the AlertDialog
?