(譯)點選,拖拽以及輸入文字
我們構建的許多widget不僅顯示資訊,還要響應使用者互動。這包括使用者可以點選的按鈕,在螢幕上拖動條目或在TextField中輸入文字。
為了測試這些互動,我們需要一種在測試環境中模擬它們的方法。為此,我們可以使用flutter_test 庫提供的WidgetTester 類。
WidgetTester
提供了輸入文字,點選和拖動的方法。
- enterText
- tap
- darg
在許多情況下,使用者互動將更新我們的應用程式的狀態。在測試環境中,當狀態更改時,Flutter不會自動重建widget。為了確保在模擬使用者互動之後重建我們的Widget樹,我們必須呼叫WidgetTester
提供的pump
或pumpAndSettle
方法。
路徑
- 建立一個要測試的widget
- 在文字欄位中輸入文字
- 確保點選按鈕新增待辦事項
- 確保滑動刪除會刪除待辦事項
1.建立一個要測試的widget
對於此示例,我們將建立一個基本的待辦事項應用程式。它將有三個我們想要測試的主要功能:
- 在TextField中輸入文字
- 點選FloatingActionButton會將文字新增到待辦事項列表中
- 滑動刪除會從列表中刪除該條目
為了將重點放在測試上,此配方將不提供有關如何構建待辦事項應用程式的詳細指南。要了解有關如何構建此應用程式的更多資訊,請參閱相關配方:
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: ListView.builder( itemCount: todos.length, itemBuilder: (BuildContext context, int index) { final todo = todos[index]; return Dismissible( key: Key('$todo$index'), onDismissed: (direction) => todos.removeAt(index), child: ListTile(title: Text(todo)), background: Container(color: Colors.red), ); }, ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { todos.add(controller.text); controller.clear(); }); }, child: Icon(Icons.add), ), ), ); } }
2.在text field
中輸入文字
現在我們有了一個todo應用程式,我們就可以開始編寫測試了!在這種情況下,我們首先在TextField
中輸入文字。
我們可以通過以下方式完成此任務
- 在測試環境中構建Widget
-
使用
WidgetTester
中的enterText 方法
testWidgets('Add and remove a todo', (WidgetTester tester) async { // Build the Widget await tester.pumpWidget(TodoList()); // Enter 'hi' into the TextField await tester.enterText(find.byType(TextField), 'hi'); });
注意:此配方基於之前的Widget測試配方。要了解Widget測試的核心概念,請參閱以下配方:
3.確保點選按鈕新增待辦事項
在我們將文字輸入TextField之後,我們要確保點選FloatingActionButton將該項新增到列表中。
這將涉及三個步驟:
- 使用tap方法點選新增按鈕
- 使用pump方法更改狀態後重建Widget
- 確保列表項顯示在頁面上
testWidgets('Add and remove a todo', (WidgetTester 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. 確保滑動刪除會刪除待辦事項
最後,我們可以確保對todo項執行滑動刪除操作會將其從列表中刪除。這將涉及三個步驟:
- 使用drag 方法執行滑動到解除操作。
- 使用pumpAndSettle 方法不斷重建我們的Widget樹,直到dismiss動畫完成。
- 確保螢幕上不再顯示該條目。
testWidgets('Add and remove a todo', (WidgetTester tester) async { // Enter text and add the item... // Swipe the item to dismiss it await tester.drag(find.byType(Dismissible), Offset(500.0, 0.0)); // Build the Widget until the dismiss animation ends await tester.pumpAndSettle(); // Ensure 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', (WidgetTester tester) async { // Build the Widget await tester.pumpWidget(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), Offset(500.0, 0.0)); // Build the Widget until the dismiss animation ends await tester.pumpAndSettle(); // Ensure the item is no longer on screen expect(find.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: ListView.builder( itemCount: todos.length, itemBuilder: (BuildContext context, int index) { final todo = todos[index]; return Dismissible( key: Key('$todo$index'), onDismissed: (direction) => todos.removeAt(index), child: ListTile(title: Text(todo)), background: Container(color: Colors.red), ); }, ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { todos.add(controller.text); controller.clear(); }); }, child: Icon(Icons.add), ), ), ); } }