跳至主要內容

點擊、拖曳和輸入文字

許多 Widget 不僅顯示資訊,還會回應使用者互動。這包括可點擊的按鈕,以及用於輸入文字的 TextField

若要測試這些互動,您需要在測試環境中模擬它們。為此,請使用 WidgetTester 函式庫。

WidgetTester 提供輸入文字、點擊和拖曳的方法。

在許多情況下,使用者互動會更新應用程式的狀態。在測試環境中,當狀態變更時,Flutter 不會自動重建 Widget。為了確保在模擬使用者互動後重建 Widget 樹狀結構,請呼叫 WidgetTester 提供的 pump()pumpAndSettle() 方法。此食譜使用以下步驟

  1. 建立一個要測試的 Widget。
  2. 在文字欄位中輸入文字。
  3. 確保點擊按鈕會新增待辦事項。
  4. 確保滑動移除會刪除待辦事項。

1. 建立一個要測試的 Widget

#

在此範例中,建立一個基本的待辦事項應用程式,測試三個功能

  1. TextField 中輸入文字。
  2. 點擊 FloatingActionButton 將文字新增至待辦事項清單。
  3. 滑動移除以從清單中移除項目。

為了將重點放在測試上,此食譜不會提供如何建立待辦事項應用程式的詳細指南。若要瞭解如何建立此應用程式的更多資訊,請參閱相關的食譜

dart
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. 在文字欄位中輸入文字

#

現在您已經有了待辦事項應用程式,開始撰寫測試。首先在 TextField 中輸入文字。

透過以下方式完成此任務

  1. 在測試環境中建立 Widget。
  2. 使用 WidgetTester 中的 enterText() 方法。
dart
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 會將項目新增至清單。

這包含三個步驟

  1. 使用 tap() 方法點擊新增按鈕。
  2. 使用 pump() 方法,在狀態變更後重建 Widget。
  3. 確保清單項目出現在螢幕上。
dart
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. 確保滑動移除會刪除待辦事項

#

最後,確保對待辦事項項目執行滑動移除動作會將其從清單中移除。這包含三個步驟

  1. 使用 drag() 方法執行滑動移除動作。
  2. 使用 pumpAndSettle() 方法,持續重建 Widget 樹狀結構,直到移除動畫完成。
  3. 確保項目不再出現在螢幕上。
dart
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);
});

完整範例

#
dart
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),
        ),
      ),
    );
  }
}