flutter-add-widget-test
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWriting Flutter Widget Tests
编写Flutter组件测试
Contents
目录
Setup & Configuration
设置与配置
Ensure the testing environment is properly configured before authoring widget tests.
- Add the dependency to the
flutter_testsection ofdev_dependencies.pubspec.yaml - Place all test files in the directory at the root of the project.
test/ - Suffix all test file names with (e.g.,
_test.dart).widget_test.dart
在编写组件测试前,请确保测试环境已正确配置。
- 将依赖添加到
flutter_test的pubspec.yaml部分。dev_dependencies - 将所有测试文件放在项目根目录的文件夹中。
test/ - 所有测试文件的文件名以结尾(例如:
_test.dart)。widget_test.dart
Core Components
核心组件
Utilize the following components to interact with and validate the widget tree:
flutter_test- : The primary interface for building and interacting with widgets in the test environment. Provided automatically by the
WidgetTesterfunction.testWidgets() - : Locates widgets in the test environment (e.g.,
Finder,find.text('Submit'),find.byType(TextField)).find.byKey(Key('submit_btn')) - : Verifies the presence or state of widgets located by a
Matcher(e.g.,Finder,findsOneWidget,findsNothing,findsNWidgets(2)).matchesGoldenFile
使用以下组件来与组件树交互并进行验证:
flutter_test- :测试环境中用于构建和交互组件的主要接口,由
WidgetTester函数自动提供。testWidgets() - :在测试环境中定位组件(例如:
Finder、find.text('Submit')、find.byType(TextField))。find.byKey(Key('submit_btn')) - :验证由
Matcher定位的组件的存在或状态(例如:Finder、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 to render the UI. Wrap the widget in a
await tester.pumpWidget(MyWidget())orMaterialAppwidget if it requires inherited directional or theme data.Directionality - Step 3: Locate elements. Instantiate objects for the target widgets.
Finder - Step 4: Verify initial state. Use to validate the initial render.
expect(finder, matcher) - Step 5: Simulate interactions. Execute gestures or inputs (e.g., ).
await tester.tap(buttonFinder) - Step 6: Rebuild the tree. Call or
await tester.pump()to process state changes.await tester.pumpAndSettle() - Step 7: Verify updated state. Use to validate the UI after the interaction.
expect() - 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.
- 步骤1:定义测试。 使用。
testWidgets('description', (WidgetTester tester) async { ... }) - 步骤2:构建组件。 调用渲染UI。如果组件需要继承方向或主题数据,请将其包裹在
await tester.pumpWidget(MyWidget())或MaterialApp组件中。Directionality - 步骤3:定位元素。 为目标组件实例化对象。
Finder - 步骤4:验证初始状态。 使用验证初始渲染结果。
expect(finder, matcher) - 步骤5:模拟交互。 执行手势或输入操作(例如:)。
await tester.tap(buttonFinder) - 步骤6:重建组件树。 调用或
await tester.pump()处理状态变化。await tester.pumpAndSettle() - 步骤7:验证更新后的状态。 使用验证交互后的UI状态。
expect() - 步骤8:运行并验证。 执行命令。
flutter test test/your_test_file_test.dart - 步骤9:反馈循环。 查看测试输出 → 识别失败的匹配器 → 调整组件逻辑或测试断言 → 重新运行直至通过。
Interaction & State Management
交互与状态管理
Apply the following conditional logic based on the type of interaction or state change being tested:
- If testing static rendering: Call once, then immediately run
await tester.pumpWidget()assertions.expect() - If testing standard state changes (e.g., button taps):
- Call .
await tester.tap(finder) - Call to trigger a single frame rebuild.
await tester.pump()
- Call
- If testing animations, transitions, or asynchronous UI updates:
- Trigger the action (e.g., ).
await tester.drag(finder, Offset(500, 0)) - Call to repeatedly pump frames until no more frames are scheduled (animation completes).
await tester.pumpAndSettle()
- Trigger the action (e.g.,
- If testing text input: Call .
await tester.enterText(textFieldFinder, 'Input string') - If testing items in a dynamic or long list: Call to ensure the target widget is rendered before interacting with it.
await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder)
根据测试的交互类型或状态变化应用以下条件逻辑:
- 如果测试静态渲染: 调用一次,然后立即运行
await tester.pumpWidget()断言。expect() - 如果测试标准状态变化(如按钮点击):
- 调用。
await tester.tap(finder) - 调用触发单帧重建。
await tester.pump()
- 调用
- 如果测试动画、过渡或异步UI更新:
- 触发操作(例如:)。
await tester.drag(finder, Offset(500, 0)) - 调用重复触发帧渲染,直至没有更多待调度帧(动画完成)。
await tester.pumpAndSettle()
- 触发操作(例如:
- 如果测试文本输入: 调用。
await tester.enterText(textFieldFinder, 'Input string') - 如果测试动态或长列表中的项: 调用,确保目标组件在交互前已渲染。
await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder)
Examples
示例
High-Fidelity Widget Test Implementation
高保真组件测试实现
Target Widget ():
lib/todo_list.dartdart
import 'package:flutter/material.dart';
class TodoList extends StatefulWidget {
const TodoList({super.key});
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
final todos = <String>[];
final controller = TextEditingController();
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.dartdart
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);
});
}目标组件():
lib/todo_list.dartdart
import 'package:flutter/material.dart';
class TodoList extends StatefulWidget {
const TodoList({super.key});
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
final todos = <String>[];
final controller = TextEditingController();
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/todo_list_test.dartdart
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);
});
}