SKILL.md
readonly只读
name
dart-generate-test-mocks
description
使用 `package:mockito` 和 `build_runner` 定义并生成外部依赖的模拟对象。适用于对依赖复杂外部服务(如 API 或数据库)的类进行单元测试。
测试与模拟 Dart 应用
目录
为可测试性组织代码
设计 Dart 类以支持依赖注入。将复杂的外部依赖(如 API 客户端或数据库)隔离,以便在测试时用模拟对象替换。
- 通过类构造函数注入外部服务(例如
http.Client)。 - 严格使用
Uri对象表示 URL,通过Uri.parse(string)创建。 - 利用 Dart 的面向对象特性(类、混入)为外部交互定义清晰的接口。
管理依赖
在 pubspec.yaml 文件中配置必要的测试和代码生成包。
- 使用
dart pub add http添加运行时依赖(例如package:http)。 - 使用
dart pub add dev:test dev:mockito dev:build_runner添加测试依赖。 - 使用前缀导入 HTTP 库以避免命名空间冲突:
import 'package:http/http.dart' as http;。
生成模拟对象
使用 package:mockito 和 build_runner 自动生成用于固定场景和行为验证的模拟类。
- 始终使用
@GenerateNiceMocks注解(优于@GenerateMocks,以避免缺失桩异常)。 - 将注解放在测试文件中,传入
MockSpec<Type>()对象的列表。 - 使用
.mocks.dart扩展名导入生成的文件。 - 执行
build_runner生成模拟文件:dart run build_runner build。
实现单元测试
使用生成的模拟对象隔离被测系统。使用 package:test 组织测试套件。
- 桩设置: 在与被测系统交互之前配置模拟行为。
- 对于同步方法,使用
when(mock.method()).thenReturn(value)。 - 关键: 对于返回
Future或Stream的方法,始终使用thenAnswer((_) async => value)。切勿对异步返回使用thenReturn。
- 对于同步方法,使用
- 验证: 断言被测系统与模拟对象的交互正确。
- 使用
verify(mock.method()).called(1)检查精确的调用次数。 - 使用参数匹配器如
any、anyNamed或captureAny进行灵活验证。
- 使用
工作流:创建并运行带模拟的测试
使用以下清单实现并验证带模拟的单元测试。
任务进度
- [ ] 1. 确定要模拟的外部依赖(例如
http.Client)。 - [ ] 2. 将依赖注入目标类的构造函数。
- [ ] 3. 创建测试文件(例如
target_test.dart)并添加@GenerateNiceMocks([MockSpec<Dependency>()])。 - [ ] 4. 为生成的
.mocks.dart文件添加part或import指令。 - [ ] 5. 运行
dart run build_runner build生成模拟类。 - [ ] 6. 使用
group()和test()编写测试用例。 - [ ] 7. 使用
when()设置所需行为。 - [ ] 8. 执行目标方法。
- [ ] 9. 使用
verify()验证交互,使用expect()断言结果。 - [ ] 10. 使用
dart test运行测试套件。
反馈循环:测试失败
如果测试失败或 build_runner 遇到错误:
- 运行验证器: 执行
dart test或dart run build_runner build。 - 检查错误: 检查缺失的桩、参数匹配器不匹配或生成文件中的语法错误。
- 修复:
- 如果模拟方法抛出意外的 null 错误,确保使用了
@GenerateNiceMocks。 - 如果异步桩抛出
ArgumentError,将thenReturn改为thenAnswer。 - 如果
build_runner失败,确保.mocks.dart导入与文件名完全匹配。
- 如果模拟方法抛出意外的 null 错误,确保使用了
- 重复直到所有测试通过。
示例
高保真模拟与测试示例
1. 被测系统(lib/api_service.dart)
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
final http.Client client;
ApiService(this.client);
Future<String> fetchData(String urlString) async {
final uri = Uri.parse(urlString);
final response = await client.get(uri);
if (response.statusCode == 200) {
return jsonDecode(response.body)['data'];
} else {
throw Exception('Failed to load data');
}
}
}
2. 测试实现(test/api_service_test.dart)
import 'package:test/test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;
import 'package:my_app/api_service.dart';
// 为 http.Client 生成模拟类
@GenerateNiceMocks([MockSpec<http.Client>()])
import 'api_service_test.mocks.dart';
void main() {
group('ApiService', () {
late ApiService apiService;
late MockClient mockHttpClient;
setUp(() {
mockHttpClient = MockClient();
apiService = ApiService(mockHttpClient);
});
test('如果 HTTP 调用成功完成,返回数据', () async {
// 准备:使用 thenAnswer 设置异步 HTTP GET 请求的桩
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response('{"data": "Success"}', 200),
);
// 执行
final result = await apiService.fetchData('https://api.example.com/data');
// 断言
expect(result, 'Success');
// 验证模拟对象被使用正确的 Uri 调用
verify(mockHttpClient.get(Uri.parse('https://api.example.com/data'))).called(1);
});
test('如果 HTTP 调用完成但返回错误,抛出异常', () {
// 准备
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response('Not Found', 404),
);
// 执行与断言
expect(
apiService.fetchData('https://api.example.com/data'),
throwsException,
);
});
});
}






