Flutter Mockito单元测试Mock仓库返回Null类型错误如何解决
问题a:Null类型不匹配错误修复
这个错误的根本原因是空安全版本的Mockito不支持手写继承Mock的模拟类,你手写的MockLevelRepository在调用未正确stub的方法时会默认返回Null,和fetchLevels声明的非空Future<List<Level>>返回类型冲突,所以抛出类型不匹配错误。你加了@GenerateMocks注解但没有执行代码生成,手写的Mock类没有生效,所以之前的尝试没用。
修复步骤如下:
- 先确保pubspec.yaml的dev_dependencies中已经添加了
mockito和build_runner依赖 - 移除测试代码中手写的
class MockLevelRepository extends Mock implements LevelRepository {}代码段 - 测试文件顶部的
@GenerateMocks仅保留需要模拟的依赖即可,不需要包含ViewModel:@GenerateMocks([LevelRepository]) - 执行代码生成命令生成模拟类文件:
flutter pub run build_runner build - 在测试文件中导入生成的mock文件,文件名一般为
[当前测试文件名].mocks.dart,比如你的测试文件叫LevelSelectionViewModelTest.dart,就导入:import 'LevelSelectionViewModelTest.mocks.dart'; - 修正stub方法的冗余写法,
thenAnswer的闭包已经声明为async,不需要再套一层Future.value:when(mockLevelRepository.fetchLevels()).thenAnswer((_) async => levelsList); - 调用异步方法
fetchLevels时要添加await关键字,否则方法还没执行完测试流程就会结束:await levelSelectionViewModel.fetchLevels();
问题b:fetchLevels异步方法的正确测试方案
你需要验证三个核心逻辑点:LevelRepository的fetchLevels被正确调用、ViewModel的levels列表数据符合预期、notifyListeners被正常触发,完整测试示例如下:
test('fetchLevels should get data from repository and notify listeners', () async { // 1. 初始化模拟依赖和测试数据 final mockLevelRepository = MockLevelRepository(); final testLevels = [ Level(id: 1, name: "Level 1"), Level(id: 2, name: "Level 2"), Level(id: 3, name: "Level 3"), ]; when(mockLevelRepository.fetchLevels()).thenAnswer((_) async => testLevels); // 2. 初始化待测试的ViewModel并注入模拟依赖 final levelSelectionViewModel = LevelSelectionViewModel(); levelSelectionViewModel.setRepository(mockLevelRepository); // 3. 监听notifyListeners调用次数 int notifyCalledCount = 0; levelSelectionViewModel.addListener(() { notifyCalledCount++; }); // 4. 执行待测试方法 await levelSelectionViewModel.fetchLevels(); // 5. 验证所有预期逻辑 // 验证repository的fetchLevels被调用了1次 verify(mockLevelRepository.fetchLevels()).called(1); // 验证levels数据符合预期 expect(levelSelectionViewModel.levels.length, 3); expect(levelSelectionViewModel.levels[0].level.id, 1); expect(levelSelectionViewModel.levels[2].level.name, "Level 3"); // 验证状态标记被修改 expect(levelSelectionViewModel.changed, true); // 验证notifyListeners被触发了1次 expect(notifyCalledCount, 1); });
如果你使用get_it做依赖管理,测试执行前需要先重置get_it容器,再将模拟的LevelRepository注册到容器中,避免和正式环境的依赖实例冲突:
setUp(() { getIt.reset(); getIt.registerSingleton<LevelRepository>(MockLevelRepository()); });
内容的提问来源于stack exchange,提问作者greysqrl
相关产品推荐
相关产品推荐

