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
?
I had a very similar issue and all I needed to do was remove the 'await' from when I 'showDialog'. The reason for this seams to be that the pumpAndSettle is awaiting for the dialog to close before it continues, which it doesn't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With