目录

Flutter 单元测试实战:Mockito 框架全面解析

为什么需要 Mockito?

项目结构示例

集成 Mockito 到 Flutter 项目

Mockito 核心概念与用法

1. 创建 Mock 对象

2. 定义 Mock 行为

3. 验证方法调用

4. 处理异步操作

5. 抛出异常

6. 参数匹配器

完整测试示例

使用 @GenerateMocks 注解

最佳实践建议


在 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();
    // ...测试代码
  });
}

最佳实践建议

  1. 保持测试专注:每个测试方法只验证一个行为或功能点
  2. 使用描述性测试名称:例如test('登录成功时应返回用户信息')
  3. 避免过度 Mock:只 Mock 被测试单元的直接依赖
  4. 使用 setUp/tearDown:减少测试代码重复
  5. 测试边界条件:空值、无效参数、异常情况等
  6. 结合其他测试工具:如flutter_test中的testWidgets测试 UI 组件

通过合理使用 Mockito,你可以编写出更加健壮、可靠的 Flutter 应用,同时提高开发效率和代码质量。Happy testing!

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐