Flutter 单元测试实战:Mockito 框架全面解析
使用或test('测试获取用户信息', () {// 定义mock行为// 也可以返回固定值});
·
目录
在 Flutter 开发中,单元测试是保证代码质量和可维护性的重要环节。Mockito 作为一款强大的 mock 框架,可以帮助我们隔离外部依赖,专注测试目标代码。本文将全面介绍 Mockito 在 Flutter 中的使用方法,并提供完整的代码示例。
为什么需要 Mockito?
在单元测试中,我们经常会遇到以下情况:
- 测试依赖于外部服务(如 API、数据库)
- 被测试对象依赖复杂的初始化过程
- 需要验证某些方法是否被正确调用
Mockito 可以帮助我们:
- 创建模拟对象替代真实依赖
- 定义模拟对象的行为和返回值
- 验证方法调用的次数和参数
- 测试异常处理逻辑
项目结构示例
下面是一个使用 Mockito 进行单元测试的 Flutter 项目结构示例:
lib/
├── model/
│ └── user.dart # 数据模型
├── repository/
│ ├── user_repository.dart # 数据仓库接口
│ └── api_service.dart # API服务
├── service/
│ └── user_service.dart # 用户服务
└── main.dart
test/
├── mock/
│ └── mock_repository.dart # Mock实现
├── repository/
│ └── user_repository_test.dart # 仓库测试
├── service/
│ └── user_service_test.dart # 服务测试
└── utils/
└── test_helper.dart # 测试辅助工具
集成 Mockito 到 Flutter 项目
首先,在pubspec.yaml
中添加依赖:
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.4.0 # Mockito框架
test: ^1.24.0 # Dart测试框架
然后运行flutter pub get
安装依赖。
Mockito 核心概念与用法
1. 创建 Mock 对象
在 Flutter 中使用 Mockito,首先需要创建 Mock 类。有两种主要方式:
// 方式一:使用Mockito的mock()函数
import 'package:mockito/mockito.dart';
class MockUserRepository extends Mock implements UserRepository {}
// 方式二:使用@GenerateMocks注解(需要build_runner)
import 'package:mockito/annotations.dart';
import 'package:your_app/repository/user_repository.dart';
@GenerateMocks([UserRepository, ApiService])
void main() {
// 生成的mock类将在.g.dart文件中
}
2. 定义 Mock 行为
使用when().thenAnswer()
或when().thenReturn()
定义方法返回值:
test('测试获取用户信息', () {
final mockRepo = MockUserRepository();
// 定义mock行为
when(mockRepo.getUser(123))
.thenAnswer((_) async => User(id: 123, name: 'John'));
// 也可以返回固定值
when(mockRepo.isLoggedIn).thenReturn(true);
});
3. 验证方法调用
使用verify()
验证方法是否被调用:
test('验证登录方法被调用', () {
final mockRepo = MockUserRepository();
final service = UserService(repository: mockRepo);
service.login('test@example.com', 'password');
// 验证方法是否被调用
verify(mockRepo.login('test@example.com', 'password')).called(1);
// 验证方法从未被调用
verifyNever(mockRepo.logout());
// 验证至少调用一次
verify(mockRepo.init()).called(greaterThan(0));
});
4. 处理异步操作
对于返回 Future 或 Stream 的方法:
test('测试异步获取用户列表', () async {
final mockRepo = MockUserRepository();
// 模拟异步返回
when(mockRepo.getUsers())
.thenAnswer((_) async => [User(id: 1, name: 'Alice')]);
final users = await mockRepo.getUsers();
expect(users.length, 1);
expect(users.first.name, 'Alice');
});
5. 抛出异常
使用thenThrow()
模拟异常情况:
test('测试登录失败', () {
final mockRepo = MockUserRepository();
// 模拟登录失败
when(mockRepo.login('wrong@example.com', 'wrong'))
.thenThrow(LoginException('Invalid credentials'));
expect(() => service.login('wrong@example.com', 'wrong'),
throwsA(isA<LoginException>()));
});
6. 参数匹配器
使用参数匹配器验证或定义方法参数:
test('参数匹配器示例', () {
final mockRepo = MockUserRepository();
// 匹配任意参数
when(mockRepo.saveUser(any)).thenReturn(true);
// 匹配特定类型
when(mockRepo.updateUser(argThat(isA<User>())))
.thenAnswer((_) async => true);
// 自定义匹配器
when(mockRepo.deleteUser(argThat((id) => id > 0)))
.thenReturn(true);
});
完整测试示例
下面是一个完整的服务层测试示例:
// user_service.dart
class UserService {
final UserRepository repository;
UserService({required this.repository});
Future<User?> getUser(int id) async {
if (id <= 0) return null;
try {
return await repository.getUser(id);
} catch (e) {
return null;
}
}
Future<bool> updateUserName(int userId, String newName) async {
final user = await repository.getUser(userId);
if (user == null) return false;
final updatedUser = user.copyWith(name: newName);
return await repository.updateUser(updatedUser);
}
}
// user_service_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:your_app/model/user.dart';
import 'package:your_app/repository/user_repository.dart';
import 'package:your_app/service/user_service.dart';
class MockUserRepository extends Mock implements UserRepository {}
void main() {
late MockUserRepository mockRepository;
late UserService service;
setUp(() {
mockRepository = MockUserRepository();
service = UserService(repository: mockRepository);
});
group('getUser', () {
test('当ID有效时返回用户', () async {
// 准备数据
final testUser = User(id: 1, name: 'Test User');
// 设置mock行为
when(mockRepository.getUser(1)).thenAnswer((_) async => testUser);
// 执行测试
final result = await service.getUser(1);
// 验证结果
expect(result, testUser);
verify(mockRepository.getUser(1)).called(1);
});
test('当ID无效时返回null', () async {
// 执行测试
final result = await service.getUser(-1);
// 验证结果
expect(result, null);
verifyNever(mockRepository.getUser(any));
});
test('当发生异常时返回null', () async {
// 设置mock行为抛出异常
when(mockRepository.getUser(1)).thenThrow(Exception('Database error'));
// 执行测试
final result = await service.getUser(1);
// 验证结果
expect(result, null);
});
});
group('updateUserName', () {
test('更新成功返回true', () async {
// 准备数据
final existingUser = User(id: 1, name: 'Old Name');
final updatedUser = existingUser.copyWith(name: 'New Name');
// 设置mock行为
when(mockRepository.getUser(1)).thenAnswer((_) async => existingUser);
when(mockRepository.updateUser(updatedUser)).thenAnswer((_) async => true);
// 执行测试
final result = await service.updateUserName(1, 'New Name');
// 验证结果
expect(result, true);
verify(mockRepository.updateUser(updatedUser)).called(1);
});
test('用户不存在返回false', () async {
// 设置mock行为返回null
when(mockRepository.getUser(1)).thenAnswer((_) async => null);
// 执行测试
final result = await service.updateUserName(1, 'New Name');
// 验证结果
expect(result, false);
verifyNever(mockRepository.updateUser(any));
});
});
}
使用 @GenerateMocks 注解
为了更高效地生成 Mock 类,可以使用@GenerateMocks
注解:
// test/mock/mock_repository.dart
import 'package:mockito/annotations.dart';
import 'package:your_app/repository/user_repository.dart';
import 'package:your_app/service/api_service.dart';
@GenerateMocks([UserRepository, ApiService])
class MockRepository {}
// 在测试文件中导入生成的mocks
import 'mock_repository.mocks.dart';
void main() {
test('使用生成的mock类', () {
final mockRepo = MockUserRepository();
// ...测试代码
});
}
最佳实践建议
- 保持测试专注:每个测试方法只验证一个行为或功能点
- 使用描述性测试名称:例如
test('登录成功时应返回用户信息')
- 避免过度 Mock:只 Mock 被测试单元的直接依赖
- 使用 setUp/tearDown:减少测试代码重复
- 测试边界条件:空值、无效参数、异常情况等
- 结合其他测试工具:如
flutter_test
中的testWidgets
测试 UI 组件
通过合理使用 Mockito,你可以编写出更加健壮、可靠的 Flutter 应用,同时提高开发效率和代码质量。Happy testing!
更多推荐
所有评论(0)