You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Mockito单元测试Mock仓库返回Null类型错误如何解决

问题a:Null类型不匹配错误修复

这个错误的根本原因是空安全版本的Mockito不支持手写继承Mock的模拟类,你手写的MockLevelRepository在调用未正确stub的方法时会默认返回Null,和fetchLevels声明的非空Future<List<Level>>返回类型冲突,所以抛出类型不匹配错误。你加了@GenerateMocks注解但没有执行代码生成,手写的Mock类没有生效,所以之前的尝试没用。

修复步骤如下:

  1. 先确保pubspec.yaml的dev_dependencies中已经添加了mockito和build_runner依赖
  2. 移除测试代码中手写的class MockLevelRepository extends Mock implements LevelRepository {}代码段
  3. 测试文件顶部的@GenerateMocks仅保留需要模拟的依赖即可,不需要包含ViewModel:@GenerateMocks([LevelRepository])
  4. 执行代码生成命令生成模拟类文件:
    flutter pub run build_runner build
    
  5. 在测试文件中导入生成的mock文件,文件名一般为[当前测试文件名].mocks.dart,比如你的测试文件叫LevelSelectionViewModelTest.dart,就导入:
    import 'LevelSelectionViewModelTest.mocks.dart';
    
  6. 修正stub方法的冗余写法,thenAnswer的闭包已经声明为async,不需要再套一层Future.value:
    when(mockLevelRepository.fetchLevels()).thenAnswer((_) async => levelsList);
    
  7. 调用异步方法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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 02:36:01