Flutter Add Widget Test

作者 flutter0ef3972f93e2无许可证3K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库2天前更新

Implement a component-level test using `WidgetTester` to verify UI rendering and user interactions (tapping, scrolling, entering text). Use when validating that a specific widget displays correct data and responds to events as expected.

AI 生成的概览

指导使用 WidgetTester 编写 Flutter 组件测试,以验证 UI 渲染和用户交互。

功能
该技能提供使用 WidgetTester、Finder 和 Matcher 实现 Flutter 组件级测试的说明。内容涵盖 flutter_test 依赖的配置、从构建组件到运行测试的分步工作流,以及针对点击、文本输入、滚动和动画等交互的条件性指导。它还包含一个 TodoList 组件及其对应测试文件的完整示例。
适用场景
适用于验证某个 Flutter 组件是否正确显示数据并按预期响应事件的场景。也适合需要新编写组件测试,或为现有测试补充渲染与交互检查的任务。
运行要求
需要一个 Flutter 项目,并在 dev_dependencies 中添加 flutter_test 依赖;测试文件应放在 test/ 目录下并以 _test.dart 结尾;需可运行 flutter test 命令。该技能不附带脚本,仅为说明文档。

Writing Flutter Widget Tests

Contents

Setup & Configuration

Ensure the testing environment is properly configured before authoring widget tests.

  1. Add the flutter_test dependency to the dev_dependencies section of pubspec.yaml.
  2. Place all test files in the test/ directory at the root of the project.
  3. Suffix all test file names with _test.dart (e.g., widget_test.dart).

Core Components

Utilize the following flutter_test components to interact with and validate the widget tree:

  • WidgetTester: The primary interface for building and interacting with widgets in the test environment. Provided automatically by the testWidgets() function.
  • Finder: Locates widgets in the test environment (e.g., find.text('Submit'), find.byType(TextField), find.byKey(Key('submit_btn'))).
  • Matcher: Verifies the presence or state of widgets located by a Finder (e.g., findsOneWidget, findsNothing, findsNWidgets(2), matchesGoldenFile).

Workflow: Implementing a Widget Test

Copy the following checklist to track progress when implementing a new widget test.

Task Progress

  • Step 1: Define the test. Use testWidgets('description', (WidgetTester tester) async { ... }).
  • Step 2: Build the widget. Call await tester.pumpWidget(MyWidget()) to render the UI. Wrap the widget in a MaterialApp or Directionality widget if it requires inherited directional or theme data.
  • Step 3: Locate elements. Instantiate Finder objects for the target widgets.
  • Step 4: Verify initial state. Use expect(finder, matcher) to validate the initial render.
  • Step 5: Simulate interactions. Execute gestures or inputs (e.g., await tester.tap(buttonFinder)).
  • Step 6: Rebuild the tree. Call await tester.pump() or await tester.pumpAndSettle() to process state changes.
  • Step 7: Verify updated state. Use expect() to validate the UI after the interaction.
  • Step 8: Run and validate. Execute flutter test test/your_test_file_test.dart.
  • Step 9: Feedback Loop. Review test output -> identify failing matchers -> adjust widget logic or test assertions -> re-run until passing.

Interaction & State Management

Apply the following conditional logic based on the type of interaction or state change being tested:

  • If testing static rendering: Call await tester.pumpWidget() once, then immediately run expect() assertions.
  • If testing standard state changes (e.g., button taps):
    1. Call await tester.tap(finder).
    2. Call await tester.pump() to trigger a single frame rebuild.
  • If testing animations, transitions, or asynchronous UI updates:
    1. Trigger the action (e.g., await tester.drag(finder, Offset(500, 0))).
    2. Call await tester.pumpAndSettle() to repeatedly pump frames until no more frames are scheduled (animation completes).
  • If testing text input: Call await tester.enterText(textFieldFinder, 'Input string').
  • If testing items in a dynamic or long list: Call await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder) to ensure the target widget is rendered before interacting with it.

Examples

High-Fidelity Widget Test Implementation

Target Widget (lib/todo_list.dart):

dart
import 'package:flutter/material.dart';
class TodoList extends StatefulWidget {  const TodoList({super.key});
  @override  State<TodoList> createState() => _TodoListState();}
class _TodoListState extends State<TodoList> {  final todos = <String>[];  final controller = TextEditingController();
  @override  Widget build(BuildContext context) {    return MaterialApp(      home: Scaffold(        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: (_) => setState(() => todos.removeAt(index)),                    child: ListTile(title: Text(todo)),                  );                },              ),            ),          ],        ),        floatingActionButton: FloatingActionButton(          onPressed: () {            setState(() {              todos.add(controller.text);              controller.clear();            });          },          child: const Icon(Icons.add),        ),      ),    );  }}

Test Implementation (test/todo_list_test.dart):

dart
import 'package:flutter/material.dart';import 'package:flutter_test/flutter_test.dart';import 'package:my_app/todo_list.dart';
void main() {  testWidgets('Add and remove a todo item', (WidgetTester tester) async {    // 1. Build the widget    await tester.pumpWidget(const TodoList());
    // 2. Verify initial state    expect(find.byType(ListTile), findsNothing);
    // 3. Enter text into the TextField    await tester.enterText(find.byType(TextField), 'Buy groceries');
    // 4. Tap the add button    await tester.tap(find.byType(FloatingActionButton));
    // 5. Rebuild the widget to reflect the new state    await tester.pump();
    // 6. Verify the item was added    expect(find.text('Buy groceries'), findsOneWidget);
    // 7. Swipe the item to dismiss it    await tester.drag(find.byType(Dismissible), const Offset(500, 0));
    // 8. Build the widget until the dismiss animation ends    await tester.pumpAndSettle();
    // 9. Verify the item was removed    expect(find.text('Buy groceries'), findsNothing);  });}

来源与署名

来源:flutter/agent-plugins位于skills/flutter-add-widget-test提交0ef3972

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架