点击、拖拽事件和文本输入
我们构建的大部分 widget 不仅仅需要展示信息,还需要响应用户交互。常见的交互有用户点击按钮、在屏幕上拖动组件和在 TextField
中输入文本。
为了测试这些交互,我们需要在测试环境中模拟上述场景,可以使用 WidgetTester
库。
WidgetTester
提供了文本输入、点击、拖动的相关方法:
在很多情况下,用户交互会更新应用状态。在测试环境中,Flutter 在状态发生改变的时候并不会自动重建 widget。为了保证模拟用户交互实现后,widget 树能重建,一定要调用 WidgetTester
提供的
pump()
或 pumpAndSettle()
。
步骤
#-
创建待测 Widget
-
在文本区输入文本
-
点击按钮,增加待办清单项
-
滑动删除待办清单项
1. 创建待测 Widget
#在这个示例中,我们将会创建一个简单的待办清单应用。其中有三个主要的功能点需要测试:
-
往
TextField
中输入文本 -
点击
FloatingActionButton
,把文本加入到待办清单列表 -
滑动移除列表中的待办清单项
为了聚焦在测试上,本章节并不会提供如何构建一个待办清单应用的具体教程。如果想要知道这个应用是如何构建的,请参考以下章节:
class TodoList extends StatefulWidget {
const TodoList({super.key});
@override
State<TodoList> 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: const Text(_appTitle),
),
body: Column(
children: [
TextField(
controller: controller,
),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todos.removeAt(index),
background: Container(color: Colors.red),
child: ListTile(title: Text(todo)),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todos.add(controller.text);
controller.clear();
});
},
child: const Icon(Icons.add),
),
),
);
}
}
2. 在文本区输入文本
#我们有了一个待办清单项应用以后,就可以开始编写测试用例了。在本示例中,我们会先测试在文本区输入文本。
完成这项任务,需要做到:
-
在测试环境创建 Widget
-
使用
WidgetTester
中的enterText()
方法
testWidgets('Add and remove a todo', (tester) async {
// Build the widget
await tester.pumpWidget(const TodoList());
// Enter 'hi' into the TextField.
await tester.enterText(find.byType(TextField), 'hi');
});
3. 点击按钮,增加待办清单项
#在 TextField
中输入文本后,需要确保能够点击 FloatingActionButton
,将文本作为清单项加入列表中。
这包含了三个步骤:
testWidgets('Add and remove a todo', (tester) async {
// Enter text code...
// Tap the add button.
await tester.tap(find.byType(FloatingActionButton));
// Rebuild the widget after the state has changed.
await tester.pump();
// Expect to find the item on screen.
expect(find.text('hi'), findsOneWidget);
});
4. 滑动删除待办清单项
#最后,我们需要确保滑动删除的操作能够正常地从列表中移除清单项。这包含了三个步骤:
-
使用
drag()
方法模拟滑动删除操作。 -
使用
pumpAndSettle()
方法使 widget 树保持重建更新,直到消除的动画完成。 -
确保上述清单项不会再出现在屏幕上
testWidgets('Add and remove a todo', (tester) async {
// Enter text and add the item...
// Swipe the item to dismiss it.
await tester.drag(find.byType(Dismissible), const Offset(500, 0));
// Build the widget until the dismiss animation ends.
await tester.pumpAndSettle();
// Ensure that the item is no longer on screen.
expect(find.text('hi'), findsNothing);
});
完整样例
#import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Add and remove a todo', (tester) async {
// Build the widget.
await tester.pumpWidget(const TodoList());
// Enter 'hi' into the TextField.
await tester.enterText(find.byType(TextField), 'hi');
// Tap the add button.
await tester.tap(find.byType(FloatingActionButton));
// Rebuild the widget with the new item.
await tester.pump();
// Expect to find the item on screen.
expect(find.text('hi'), findsOneWidget);
// Swipe the item to dismiss it.
await tester.drag(find.byType(Dismissible), const Offset(500, 0));
// Build the widget until the dismiss animation ends.
await tester.pumpAndSettle();
// Ensure that the item is no longer on screen.
expect(find.text('hi'), findsNothing);
});
}
class TodoList extends StatefulWidget {
const TodoList({super.key});
@override
State<TodoList> 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: const Text(_appTitle),
),
body: Column(
children: [
TextField(
controller: controller,
),
Expanded(
child: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];
return Dismissible(
key: Key('$todo$index'),
onDismissed: (direction) => todos.removeAt(index),
background: Container(color: Colors.red),
child: ListTile(title: Text(todo)),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
todos.add(controller.text);
controller.clear();
});
},
child: const Icon(Icons.add),
),
),
);
}
}
除非另有说明,本文档之所提及适用于 Flutter 的最新稳定版本,本页面最后更新时间: 2024-04-27。 查看文档源码 或者 为本页面内容提出建议。