关于 dart-generate-test-mocks
dart-generate-test-mocks 面向使用 mock 的 Dart 测试开发,介绍框架选择、模拟类生成、行为配置和交互验证,帮助组织可测试的代码。
主要功能
- 01比较 mocktail 与 mockito
- 02通过依赖注入组织代码
- 03使用 build_runner 生成 mockito 类
- 04正确配置异步方法行为
- 05验证 mock 交互
适用场景
- 01为 Dart 测试套件选择 mock 框架
- 02为服务契约生成 mock
- 03测试异步服务行为
- 04断言方法调用并检查意外交互
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dhruvanbhalara/ skills --skill dart-generate-test-mocks安装后在 Claude Code 中输入/dart-generate-test-mocks确认已加载。
详细介绍
目录
Mock 框架选择
| 标准 | mocktail(推荐) | mockito |
|---|---|---|
| 代码生成 | 无需生成 | 需要 build_runner |
| 设置开销 | 极少(class MockService extends Mock implements Service {}) | 注解 + 生成器构建步骤 |
| 类型安全 | 运行时验证 | 编译时生成 Mock 类 |
| 复杂 API | 手动提供回退值(registerFallbackValue) | 自动处理 @GenerateNiceMocks |
面向可测试性组织代码
若要编写有效的单元测试,请使用依赖注入来组织代码库。隔离与物理层交互的操作(例如磁盘存储、外部服务和平台通道),以便在测试时用 Mock 对象替换它们:
- 构造函数注入:将所有服务依赖项(例如 HTTP 客户端、数据库辅助客户端)传入类构造函数,而不是在类内部直接实例化。
- 接口隔离:定义清晰的抽象基类,用于表示服务契约。理想情况下,Mock 应针对这些抽象契约,而不是具体的服务实现。
管理开发依赖项
在 pubspec.yaml 中配置代码生成和 Mock 测试所需的软件包:
- 在
dev_dependencies下添加目标测试框架和生成器:dart pub add dev:test dev:mockito dev:build_runner - 在测试文件中导入 Mockito 注解:
import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart';
生成 Mock 文件
结合使用 package:mockito 和 build_runner,自动生成 Mock 结构:
- GenerateNiceMocks:始终使用
@GenerateNiceMocks注解,而不是旧版的@GenerateMocks。当调用方法时,如果未预先配置 Stub,Nice Mock 会自动返回 null 或匹配的默认值,而不是抛出 "MissingStubException"。 - MockSpec 配置:使用
@GenerateNiceMocks([MockSpec<YourService>()])标注测试文件的入口点。 - Mirror 扩展导入:使用
.mocks.dart后缀导入对应的生成文件(例如import 'service_test.mocks.dart')。 - 运行生成器:通过 CLI 触发代码生成:
dart run build_runner build --delete-conflicting-outputs
Stub 和验证的最佳实践
遵循以下指南,编写可靠的 Mock 交互:
- Future 和 Stream 的 Stub 设置:为返回
Future或Stream的方法设置 Stub 时,始终使用.thenAnswer((_) async => value)。异步返回值绝不能使用.thenReturn(),否则会导致运行时类型转换错误。 - 调用跟踪:使用
verify()API 验证是否调用了指定方法。调用.called(number)断言精确的调用次数。 - 未使用的调用断言:使用
verifyNever()或verifyNoMoreInteractions(mock),确保测试生命周期内没有发生意外操作。
工作流:生成 Mock 并验证测试
按照此清单设置 Mock 配置:
- 检查构造函数:确保目标服务类通过构造函数接收依赖项。
- 编写测试文件:创建
test/feature_test.dart,并导入 Mockito 和测试库。 - 标注入口点:在
main()上方添加@GenerateNiceMocks([MockSpec<TargetService>()])。 - 声明 Mock 导入:添加指向
feature_test.mocks.dart的导入语句。 - 生成代码:在终端中运行
dart run build_runner build,以创建 Mock 文件。 - 实例化 Mock:在
setUp回调中实例化生成的 Mock 类(例如mockService = MockTargetService())。 - 设置桩行为:在测试用例中使用
when()配置行为。 - 执行并断言:调用被测系统,并通过
expect()验证输出。 - 验证调用:使用
verify()断言 Mock 的交互。
示例
完整的 Mockito Mock 测试套件
此示例展示了如何 Mock 远程 API 客户端,以测试数据库同步服务。
import 'package:test/test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;
// Define an abstract contract for a user repository
abstract class UserRepository {
Future<String> getUserName(int id);
}
// System Under Test consuming the repository
class UserService {
final UserRepository repository;
UserService(this.repository);
Future<String> fetchDisplayName(int id) async {
try {
final name = await repository.getUserName(id);
return 'User: $name';
} catch (_) {
return 'Unknown User';
}
}
}
// 1. Annotate to generate MockUserRepository
@GenerateNiceMocks([MockSpec<UserRepository>()])
import 'user_service_test.mocks.dart';
void main() {
group('UserService', () {
late MockUserRepository mockRepo;
late UserService userService;
setUp(() {
mockRepo = MockUserRepository();
userService = UserService(mockRepo);
});
test('returns formatted display name when repository succeeds', () async {
// 2. Arrange: Stub the async getUserName method
when(mockRepo.getUserName(42)).thenAnswer(
(_) async => 'Alice',
);
// 3. Act: Run target method
final displayName = await userService.fetchDisplayName(42);
// 4. Assert: Validate outcomes
expect(displayName, equals('User: Alice'));
// 5. Verify: Assert the repository method was invoked with correct arguments
verify(mockRepo.getUserName(42)).called(1);
});
test('returns fallback string when repository throws exception', () async {
// Arrange: Stub to throw error
when(mockRepo.getUserName(any)).thenThrow(
Exception('Connection Timeout'),
);
// Act
final displayName = await userService.fetchDisplayName(99);
// Assert
expect(displayName, equals('Unknown User'));
});
});
}
评论
支持 Markdown