关于 flutter-tester
为 Flutter 项目提供分层测试指导的技能,适用于编写仓库、DAO、服务、Provider 和 Widget 测试。内容涵盖 Given-When-Then 组织方式、依赖模拟、测试运行及常见错误。
主要功能
- 01按架构层隔离测试
- 02采用 Given-When-Then 结构
- 03提供 Riverpod 与 Widget 测试指南
- 04指导 Mock、Fake 与依赖覆盖
- 05包含测试运行命令与检查清单
适用场景
- 01为 Repository 和 DAO 编写测试
- 02验证 Riverpod 状态与生命周期
- 03测试 Widget 交互和导航
- 04检查异步加载、错误及边界状态
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ harishwarrior/ flutter-claude-skills --skill flutter-tester安装后在 Claude Code 中输入/flutter-tester确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/layer_testing_patterns.mdriverpod_testing_guide.mdwidget_testing_guide.md详细介绍
Flutter Tester
要求
- Flutter 项目依赖
flutter_test - 支持 Riverpod、Mockito 和 GetIt
- 添加
@GenerateMocks注解后,运行dart run build_runner build生成 mock - 兼容 FVM(使用
fvm flutter test而不是flutter test)
概览
使用 Given-When-Then 结构,分别测试各个架构层。始终测试成功和错误路径。不要 mock provider,而应覆盖它们的依赖项。
参考文件
根据测试内容加载相应文件:
| 测试内容 | 参考文件 |
|---|---|
| Repository、DAO、Service 逻辑 | references/layer_testing_patterns.md |
| Widget UI、交互、对话框、导航 | references/widget_testing_guide.md |
| Riverpod provider 状态、变更、生命周期 | references/riverpod_testing_guide.md |
核心原则
1. 分层隔离
使用各层自己的 mock 依赖项进行测试:
| 层 | 测试内容 | 要 mock 的对象 |
|---|---|---|
| Repository | 数据源之间的数据协调 | DAO、API、Logger |
| DAO | 数据库 CRUD 操作 | 使用真实的内存数据库,mock Logger |
| Provider | 状态管理和转换 | Service、Repository |
| Service | 业务逻辑和工作流 | Repository、网络客户端 |
| Widget | UI 行为和交互 | Provider 依赖项(通过覆盖) |
2. Given-When-Then 结构
test('Given valid data, When fetchUsers called, Then returns user list', () async {
// Arrange (Given)
when(mockDAO.fetchAll()).thenAnswer((_) async => expectedUsers);
// Act (When)
final result = await repository.fetchUsers();
// Assert (Then)
expect(result, equals(expectedUsers));
verify(mockDAO.fetchAll()).called(1);
});
3. 测试组织
group('UserRepository', () {
group('fetchUsers', () {
setUp(() { /* init mocks, register with GetIt */ });
tearDown(() => GetIt.I.reset()); // Always reset GetIt
test('Given success ... When ... Then ...', () { });
test('Given error ... When ... Then ...', () { });
});
});
标准测试设置
生成 Mock
@GenerateMocks([IUserDAO, IUserAPI, ILogger])
void main() { ... }
修改 @GenerateMocks 后,运行 dart run build_runner build。
在 GetIt 中注册
setUp(() {
mockDAO = MockIUserDAO();
mockLogger = MockILogger();
GetIt.I
..registerSingleton<IUserDAO>(mockDAO)
..registerSingleton<ILogger>(mockLogger);
});
tearDown(() => GetIt.I.reset()); // Critical — always reset
Fake 与 Mock
- Fake(
class FakeLogger extends ILogger)——静默存根;无需验证调用时使用 - Mock(
MockILogger)——需要使用when()、verify()或thenThrow()时使用
快速参考
| 场景 | 关键模式 |
|---|---|
| 测试 Repository | Mock DAO + API → 注入 Repository 构造函数 |
| 测试 DAO | 在 setUp 中使用 FakeDatabase 或 openInMemoryDatabase(),在 tearDown 中删除表 |
| 测试 Riverpod provider | createContainer(overrides: [serviceProvider.overrideWith(...)]) |
| 测试 Widget | 设置屏幕尺寸,使用 find.byKey(),调用 pumpAndSettle() |
| 测试加载状态 | 使用 Completer,调用 pump() 断言加载状态,完成后再次调用 pump() |
| 测试平台专属 UI | debugDefaultTargetPlatformOverride = TargetPlatform.iOS——之后重置 |
| 测试 GoRouter 导航 | FakeGoRouter + MockGoRouterProvider |
运行测试
flutter test --coverage # All tests with coverage
flutter test test/path/to/test.dart # Specific file
flutter test --plain-name "Given valid data" # Filter by name
genhtml coverage/lcov.info -o coverage/html # Generate HTML coverage report
# Prefix any command with `fvm` if using Flutter Version Manager
常见错误
| 错误 | 修正方法 |
|---|---|
| 直接 mock provider | 覆盖其依赖项:provider.overrideWith(...) |
在 tearDown 中漏掉 GetIt.I.reset() | 测试会相互影响——务必重置 |
在测试中使用 await Future.delayed() | 改用 await tester.pumpAndSettle() 或 Completer |
| 按文本字符串查找 Widget | 使用 find.byKey(const Key('name'))——文本变更后仍然稳定 |
| Widget 测试中未设置屏幕尺寸 | 添加 tester.view.physicalSize = const Size(1000, 1000) |
未重置 debugDefaultTargetPlatformOverride | 在测试末尾将其设为 null |
tearDown() 未使用 lambda | 写成 tearDown(() async { ... }),而不是 tearDown() async { ... } |
测试清单
设置与模拟:
- 模拟依赖项(而非提供方)
- 如使用
SharedPreferences,则对其进行模拟 - 在
tearDown中调用GetIt.I.reset() - 在
tearDown中关闭流 - 在
tearDown中释放控制器
Widget 测试:
- 为源 Widget 添加键,并在
find.byKey()中使用 - 设置屏幕尺寸(
physicalSize+devicePixelRatio) - 重置平台覆盖(
debugDefaultTargetPlatformOverride = null) - 如适用,验证导航
测试覆盖率:
- 覆盖成功和失败路径
- 测试边缘情况(null、空值、最大值)
- 测试加载和错误状态
- 正确处理异步操作(不使用
Future.delayed)
代码质量:
- 使用 Given-When-Then 命名方式
- 在适当情况下使用
verify()或verifyNever() - 测试彼此隔离且结果确定
评论
支持 Markdown