Tapping, dragging and entering text
Many of the Widgets we build not only display information, but also respond to user interactionhttps://flutter.axuer.com/docs/cookbook/testing/widget/. This includes buttons that users can tap on, dragging items across the screen, or entering text into a TextField
https://flutter.axuer.com/docs/cookbook/testing/widget/.
In order to test these interactions, we need a way to simulate them in the test environmenthttps://flutter.axuer.com/docs/cookbook/testing/widget/. To do so, we can use the WidgetTester
class provided by the flutter_test
libraryhttps://flutter.axuer.com/docs/cookbook/testing/widget/.
The WidgetTester
provides methods for entering text, tapping, and dragginghttps://flutter.axuer.com/docs/cookbook/testing/widget/.
In many cases, user interactions will update the state of our apphttps://flutter.axuer.com/docs/cookbook/testing/widget/. In the test environment, Flutter will not automatically rebuild widgets when the state changeshttps://flutter.axuer.com/docs/cookbook/testing/widget/. To ensure our Widget tree is rebuilt after we simulate a user interaction, we must call the pump
or pumpAndSettle
methods provided by the WidgetTester
https://flutter.axuer.com/docs/cookbook/testing/widget/.
Directions
- Create a Widget to test
- Enter text in the text field
- Ensure tapping a button adds the todo
- Ensure swipe-to-dismiss removes the todo
1https://flutter.axuer.com/docs/cookbook/testing/widget/. Create a Widget to test
For this example, we’ll create a basic todo apphttps://flutter.axuer.com/docs/cookbook/testing/widget/. It will have three main features that we’ll want to test:
- Enter text into a
TextField
- Tapping a
FloatingActionButton
adds the text to a list of todos - Swipe-to-dismiss removes the item from the list
To keep the focus on testing, this recipe will not provide a detailed guide on how to build the todo apphttps://flutter.axuer.com/docs/cookbook/testing/widget/. To learn more about how this app is built, please see the relevant recipes:
class TodoList extends StatefulWidget {
@override
_TodoListState createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
static const _appTitle = 'Todo List';
final todos = <String>[];
final controller = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(_appTitle),
),
body: Column(
children: [
TextField(
controller: controller,
),
Expanded(
child: ListViewhttps://flutter.axuer.com/docs/cookbook/testing/widget/.builder(
itemCount: todoshttps://flutter.axuer.com/docs/cookbook/testing/widget/.length,
itemBuilder: (BuildContext context, int index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todoshttps://flutter.axuer.com/docs/cookbook/testing/widget/.removeAt(index),
child: ListTile(title: Text(todo)),
background: Container(color: Colorshttps://flutter.axuer.com/docs/cookbook/testing/widget/.red),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todoshttps://flutter.axuer.com/docs/cookbook/testing/widget/.add(controllerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.text);
controllerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.clear();
});
},
child: Icon(Iconshttps://flutter.axuer.com/docs/cookbook/testing/widget/.add),
),
),
);
}
}
2https://flutter.axuer.com/docs/cookbook/testing/widget/. Enter text in the text field
Now that we have a todo app, we can begin writing our test! In this case, we’ll start by entering text into the TextField
https://flutter.axuer.com/docs/cookbook/testing/widget/.
We can accomplish this task by:
- Building the Widget in the Test Environment
- Using the
enterText
method from theWidgetTester
testWidgets('Add and remove a todo', (WidgetTester tester) async {
// Build the Widget
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.pumpWidget(TodoList());
// Enter 'hi' into the TextField
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.enterText(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.byType(TextField), 'hi');
});
Note: This recipe builds upon previous Widget testing recipeshttps://flutter.axuer.com/docs/cookbook/testing/widget/. To learn the core concepts of Widget testing, see the following recipes:
3https://flutter.axuer.com/docs/cookbook/testing/widget/. Ensure tapping a button adds the todo
After we’ve entered text into the TextField
, we’ll want to ensure that tapping the FloatingActionButton
adds the item to the listhttps://flutter.axuer.com/docs/cookbook/testing/widget/.
This will involve three steps:
- Tap the add button using the
tap
method - Rebuild the Widget after the state has changed using the
pump
method - Ensure the list item appears on screen
testWidgets('Add and remove a todo', (WidgetTester tester) async {
// Enter text codehttps://flutter.axuer.com/docs/cookbook/testing/widget/.https://flutter.axuer.com/docs/cookbook/testing/widget/.https://flutter.axuer.com/docs/cookbook/testing/widget/.
// Tap the add button
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.tap(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.byType(FloatingActionButton));
// Rebuild the Widget after the state has changed
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.pump();
// Expect to find the item on screen
expect(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.text('hi'), findsOneWidget);
});
4https://flutter.axuer.com/docs/cookbook/testing/widget/. Ensure swipe-to-dismiss removes the todo
Finally, we can ensure that performing a swipe-to-dismiss action on the todo item will remove it from the listhttps://flutter.axuer.com/docs/cookbook/testing/widget/. This will involve three steps:
- Use the
drag
method to perform a swipe-to-dismiss actionhttps://flutter.axuer.com/docs/cookbook/testing/widget/. - Use the
pumpAndSettle
method to continually rebuild our Widget tree until the dismiss animation is completehttps://flutter.axuer.com/docs/cookbook/testing/widget/. - Ensure the item no longer appears on screenhttps://flutter.axuer.com/docs/cookbook/testing/widget/.
testWidgets('Add and remove a todo', (WidgetTester tester) async {
// Enter text and add the itemhttps://flutter.axuer.com/docs/cookbook/testing/widget/.https://flutter.axuer.com/docs/cookbook/testing/widget/.https://flutter.axuer.com/docs/cookbook/testing/widget/.
// Swipe the item to dismiss it
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.drag(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.byType(Dismissible), Offset(500https://flutter.axuer.com/docs/cookbook/testing/widget/.0, 0https://flutter.axuer.com/docs/cookbook/testing/widget/.0));
// Build the Widget until the dismiss animation ends
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.pumpAndSettle();
// Ensure the item is no longer on screen
expect(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.text('hi'), findsNothing);
});
Complete example
Once we’ve completed these steps, we should have a working app with a test to ensure it works correctly!
import 'package:flutter/materialhttps://flutter.axuer.com/docs/cookbook/testing/widget/.dart';
import 'package:flutter_test/flutter_testhttps://flutter.axuer.com/docs/cookbook/testing/widget/.dart';
void main() {
testWidgets('Add and remove a todo', (WidgetTester tester) async {
// Build the Widget
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.pumpWidget(TodoList());
// Enter 'hi' into the TextField
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.enterText(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.byType(TextField), 'hi');
// Tap the add button
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.tap(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.byType(FloatingActionButton));
// Rebuild the Widget with the new item
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.pump();
// Expect to find the item on screen
expect(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.text('hi'), findsOneWidget);
// Swipe the item to dismiss it
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.drag(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.byType(Dismissible), Offset(500https://flutter.axuer.com/docs/cookbook/testing/widget/.0, 0https://flutter.axuer.com/docs/cookbook/testing/widget/.0));
// Build the Widget until the dismiss animation ends
await testerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.pumpAndSettle();
// Ensure the item is no longer on screen
expect(findhttps://flutter.axuer.com/docs/cookbook/testing/widget/.text('hi'), findsNothing);
});
}
class TodoList extends StatefulWidget {
@override
_TodoListState createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
static const _appTitle = 'Todo List';
final todos = <String>[];
final controller = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: _appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(_appTitle),
),
body: Column(
children: [
TextField(
controller: controller,
),
Expanded(
child: ListViewhttps://flutter.axuer.com/docs/cookbook/testing/widget/.builder(
itemCount: todoshttps://flutter.axuer.com/docs/cookbook/testing/widget/.length,
itemBuilder: (BuildContext context, int index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todoshttps://flutter.axuer.com/docs/cookbook/testing/widget/.removeAt(index),
child: ListTile(title: Text(todo)),
background: Container(color: Colorshttps://flutter.axuer.com/docs/cookbook/testing/widget/.red),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todoshttps://flutter.axuer.com/docs/cookbook/testing/widget/.add(controllerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.text);
controllerhttps://flutter.axuer.com/docs/cookbook/testing/widget/.clear();
});
},
child: Icon(Iconshttps://flutter.axuer.com/docs/cookbook/testing/widget/.add),
),
),
);
}
}