Flutter/Dart无Mock的Repository单元测试方法及Mockito作用解析
Flutter/Dart中Repository的无Mock单元测试及Mockito的作用
一、不使用Mocking的单元测试(验证真实接口数据)
要直接调用真实接口测试Repository,核心是让测试代码依赖真实网络组件,但需先优化代码结构,再覆盖不同场景:
1. 先优化原Repository代码(修复类型问题+注入Dio)
原代码存在返回类型不一致的问题,同时将Dio改为构造注入,方便测试时统一配置:
class Repository { final Dio dio; final NetworkManager networkManager; Repository({required this.dio, required this.networkManager}); Future<List<TodoModel>> getTodo() async { if (networkManager.isNetworkAvailable()) { final response = await dio.get('https://dummyjson.com/todos'); return response.data['todos'].map<TodoModel>((item) { return TodoModel.fromJson(item); }).toList(); } else { return const [TodoModel(id: 999, todo: "FailedTODO", completed: false, userId: 10101)]; } } } class NetworkManager { bool isNetworkAvailable() { return true; } } // 假设的TodoModel定义 class TodoModel { final int id; final String todo; final bool completed; final int userId; const TodoModel({ required this.id, required this.todo, required this.completed, required this.userId, }); factory TodoModel.fromJson(Map<String, dynamic> json) { return TodoModel( id: json['id'], todo: json['todo'], completed: json['completed'], userId: json['userId'], ); } }
2. 编写无Mock的单元测试代码
使用test包编写测试,直接调用真实接口,同时覆盖网络可用/不可用两种场景:
import 'package:dio/dio.dart'; import 'package:test/test.dart'; void main() { late Repository repository; late Dio dio; setUp(() { dio = Dio(); // 初始化网络可用场景的Repository repository = Repository( dio: dio, networkManager: NetworkManager(), ); }); test('网络可用时,getTodo返回真实接口的Todo列表', () async { final todos = await repository.getTodo(); expect(todos.isNotEmpty, true); // 验证返回数据的结构符合预期 expect(todos.first.id, isA<int>()); expect(todos.first.todo, isA<String>()); expect(todos.first.completed, isA<bool>()); }); test('网络不可用时,getTodo返回预设的 fallback 数据', () async { // 自定义NetworkManager子类模拟网络不可用 class OfflineNetworkManager extends NetworkManager { @override bool isNetworkAvailable() => false; } final offlineRepo = Repository( dio: dio, networkManager: OfflineNetworkManager(), ); final todos = await offlineRepo.getTodo(); expect(todos.length, 1); expect(todos.first.id, 999); expect(todos.first.todo, "FailedTODO"); }); }
注意:这类测试依赖真实网络和外部接口稳定性,更偏向集成测试,如果接口返回结构变化或网络中断,测试会失败。
二、为什么需要使用Mockito?
Mockito是Mock框架,核心作用是解决单元测试的依赖隔离问题,具体包括:
- 隔离外部依赖:单元测试只需要验证当前类的逻辑,Mockito可以Mock掉Dio、NetworkManager等外部组件,避免真实网络、第三方服务的状态变化干扰测试结果。
- 轻松模拟异常场景:可以快速复现网络请求失败、超时、返回错误码,或者NetworkManager返回网络不可用等场景,这些在真实环境中很难稳定触发。
- 提升测试速度:Mock调用不需要真实网络请求,测试执行效率更高,适合频繁运行的单元测试套件。
- 突破外部依赖限制:如果真实接口有调用频率限制、需要授权,或者尚未开发完成,Mock可以让测试提前进行。
Mockito测试示例(简单版)
import 'package:dio/dio.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; // 生成Mock类 class MockDio extends Mock implements Dio {} class MockNetworkManager extends Mock implements NetworkManager {} void main() { late Repository repository; late MockDio mockDio; late MockNetworkManager mockNetworkManager; setUp(() { mockDio = MockDio(); mockNetworkManager = MockNetworkManager(); repository = Repository(dio: mockDio, networkManager: mockNetworkManager); }); test('网络可用且请求成功时,正确解析返回数据', () async { // 模拟网络可用 when(mockNetworkManager.isNetworkAvailable()).thenReturn(true); // 模拟Dio返回的响应数据 when(mockDio.get('https://dummyjson.com/todos')).thenAnswer((_) async => Response( data: { 'todos': [{'id': 1, 'todo': 'Test Todo', 'completed': false, 'userId': 1}] }, statusCode: 200, requestOptions: RequestOptions(path: ''), )); final todos = await repository.getTodo(); expect(todos.length, 1); expect(todos.first.todo, 'Test Todo'); // 验证Dio的get方法被正确调用 verify(mockDio.get('https://dummyjson.com/todos')).called(1); }); test('网络不可用时,返回fallback数据且不调用Dio', () async { when(mockNetworkManager.isNetworkAvailable()).thenReturn(false); final todos = await repository.getTodo(); expect(todos.first.id, 999); // 验证Dio的get方法从未被调用 verifyNever(mockDio.get(any)); }); }
内容的提问来源于stack exchange,提问作者MainakChoudhury
相关产品推荐
相关产品推荐

