SKILL.md
只读
名称
dart-generate-test-mocks
描述
使用 `package:mockito` 和 `build_runner` 为外部依赖定义并生成 Mock 对象。适用于对依赖 API、数据库等复杂外部服务的类进行单元测试的场景。
Dart 应用的测试与 Mock 模拟
目录
针对可测试性构建代码结构
设计 Dart 类时应支持依赖注入(Dependency Injection)。将复杂的外部依赖(如 API 客户端或数据库)隔离开来,以便在测试期间替换为 Mock 对象。
- 通过类构造函数注入外部服务(例如
http.Client)。 - 使用
Uri.parse(string)严格将 URL 表示为Uri对象。 - 利用 Dart 的面向对象特性(类、Mixin)为外部交互定义清晰的接口。
管理依赖
在 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;。
生成 Mock 对象
使用 package:mockito 和 build_runner 自动生成 Mock 类,以便进行固定场景模拟与行为验证。
- 始终推荐使用
@GenerateNiceMocks注解(优先于@GenerateMocks,以避免出现未存根/未 Stub 调用的抛错)。 - 在测试文件中添加该注解,并传入
MockSpec<Type>()对象列表。 - 引入后缀为
.mocks.dart的生成文件。 - 执行
build_runner命令生成 Mock 文件:dart run build_runner build。
编写单元测试
使用生成的 Mock 对象隔离被测系统(System Under Test)。使用 package:test 构建测试套件。
- 存根配置(Stubbing): 在与被测系统交互前配置 Mock 对象的行为。
- 对于同步方法,使用
when(mock.method()).thenReturn(value)。 - 关键: 返回
Future或Stream的异步方法必须使用thenAnswer((_) async => value)。切勿对异步返回使用thenReturn。
- 对于同步方法,使用
- 行为验证(Verification): 断言被测系统是否与 Mock 对象进行了正确的交互。
- 使用
verify(mock.method()).called(1)检查精确的调用次数。 - 使用参数匹配器(如
any、anyNamed或captureAny)进行灵活校验。
- 使用
工作流:创建并运行 Mock 测试
使用以下检查清单来实现和验证带有 Mock 的单元测试。
任务进度
- [ ] 1. 确定需要 Mock 的外部依赖(例如
http.Client)。 - [ ] 2. 将依赖注入到目标类的构造函数中。
- [ ] 3. 创建测试文件(例如
target_test.dart)并添加@GenerateNiceMocks([MockSpec<Dependency>()])。 - [ ] 4. 为生成的
.mocks.dart文件添加part或import指令。 - [ ] 5. 运行
dart run build_runner build生成 Mock 类。 - [ ] 6. 使用
group()和test()编写测试用例。 - [ ] 7. 使用
when()配置需要的 Stub 行为。 - [ ] 8. 执行目标方法。
- [ ] 9. 使用
verify()验证交互,并使用expect()断言结果。 - [ ] 10. 运行
dart test执行测试套件。
关联反馈:测试失败排查
如果测试失败或 build_runner 报错:
- 运行校验: 执行
dart test或dart run build_runner build。 - 排查错误: 检查是否存在缺失的 Stub、不匹配的参数匹配器或生成文件中的语法错误。
- 修复:
- 如果 Mock 方法抛出意料之外的 null 错误,确保使用的是
@GenerateNiceMocks。 - 如果异步 Stub 抛出
ArgumentError,请将thenReturn改为thenAnswer。 - 如果
build_runner运行失败,确保.mocks.dart的导入路径与文件名完全匹配。
- 如果 Mock 方法抛出意料之外的 null 错误,确保使用的是
- 重复上述步骤,直到所有测试通过。
完整示例
高保真 Mock 与测试示例
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 生成 Mock 类
@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('returns data if the http call completes successfully', () async {
// Arrange: 使用 thenAnswer 配置异步 HTTP GET 请求的 Stub
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response('{"data": "Success"}', 200),
);
// Act
final result = await apiService.fetchData('https://api.example.com/data');
// Assert
expect(result, 'Success');
// 验证 Mock 是否传入正确的 Uri 并被调用了 1 次
verify(mockHttpClient.get(Uri.parse('https://api.example.com/data'))).called(1);
});
test('throws an exception if the http call completes with an error', () {
// Arrange
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response('Not Found', 404),
);
// Act & Assert
expect(
apiService.fetchData('https://api.example.com/data'),
throwsException,
);
});
});
}






