Flutter Widget 测试详解
简介
Widget 测试用于测试 UI 组件的行为和渲染。本文介绍如何在 Flutter 中编写 Widget 测试。
基本配置
添加依赖
yaml
dev_dependencies:
flutter_test:
sdk: flutter
创建测试文件
dart
// test/widget_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/my_widget.dart';
void main() {
testWidgets('MyWidget should display a title', (tester) async {
await tester.pumpWidget(MyWidget(title: 'Hello'));
expect(find.text('Hello'), findsOneWidget);
});
}
测试工具
WidgetTester
dart
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// 构建 Widget
await tester.pumpWidget(MyApp());
// 查找组件
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// 触发点击
await tester.tap(find.byIcon(Icons.add));
// 等待动画完成
await tester.pumpAndSettle();
// 验证结果
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
Finder
dart
// 通过文本查找
find.text('Hello');
// 通过类型查找
find.byType(RaisedButton);
// 通过 Key 查找
find.byKey(Key('my_button'));
// 通过图标查找
find.byIcon(Icons.add);
// 通过语义标签查找
find.bySemanticsLabel('Add');
// 通过工具方法查找
find.widgetWithText(RaisedButton, 'Click me');
完整示例
dart
// 被测试的组件
class LoginForm extends StatefulWidget {
final Function(String, String) onSubmit;
const LoginForm({Key? key, required this.onSubmit}) : super(key: key);
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your email';
}
return null;
},
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
widget.onSubmit(
_emailController.text,
_passwordController.text,
);
}
},
child: Text('Login'),
),
],
),
);
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
}
// 测试文件
void main() {
group('LoginForm', () {
testWidgets('should show validation errors', (tester) async {
// 构建组件
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: LoginForm(
onSubmit: (email, password) {},
),
),
));
// 点击登录按钮
await tester.tap(find.text('Login'));
await tester.pump();
// 验证错误提示
expect(
find.text('Please enter your email'),
findsOneWidget,
);
expect(
find.text('Please enter your password'),
findsOneWidget,
);
});
testWidgets('should call onSubmit with form data', (tester) async {
String? submittedEmail;
String? submittedPassword;
// 构建组件
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: LoginForm(
onSubmit: (email, password) {
submittedEmail = email;
submittedPassword = password;
},
),
),
));
// 输入表单数据
await tester.enterText(
find.widgetWithText(TextFormField, 'Email'),
'test@example.com',
);
await tester.enterText(
find.widgetWithText(TextFormField, 'Password'),
'password123',
);
// 点击登录按钮
await tester.tap(find.text('Login'));
await tester.pump();
// 验证提交的数据
expect(submittedEmail, equals('test@example.com'));
expect(submittedPassword, equals('password123'));
});
});
}
测试异步操作
dart
testWidgets('should show loading indicator', (tester) async {
// 构建组件
await tester.pumpWidget(MyAsyncWidget());
// 验证初始状态
expect(find.byType(CircularProgressIndicator), findsNothing);
// 触发异步操作
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
// 验证加载状态
expect(find.byType(CircularProgressIndicator), findsOneWidget);
// 等待异步操作完成
await tester.pumpAndSettle();
// 验证最终状态
expect(find.byType(CircularProgressIndicator), findsNothing);
expect(find.text('Done'), findsOneWidget);
});
最佳实践
- 测试关键交互流程
- 验证边界条件
- 模拟异步操作
- 测试错误处理
- 保持测试简单
注意事项
- 等待动画完成
- 处理异步操作
- 清理测试资源
- 避免过度测试
- 维护测试代码
总结
Widget 测试是确保 UI 组件正确性的重要手段。通过编写全面的 Widget 测试,可以提高应用的可靠性和可维护性。