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

Mocktail结合GetIt依赖注入时Mock未响应动态参数问题

问题根因

核心问题出在依赖获取的时序和实例持有逻辑,和Mocktail的stub配置本身无关:

  1. 先理清楚测试执行的固定顺序:
    • 每个测试用例启动前,setUp 会先调用 registerServices() 注册默认返回true的Mock实例,随后初始化sut(即LoginAndUserService)
    • LoginAndUserService 里的_prefs是final修饰的字段,在类构造阶段就直接通过locator获取了当时注册的Mock实例,之后这个字段的引用不会再发生任何变化
    • 你在测试用例里重新调用getAndRegisterSharedPreferencesServiceMock(firstTimeUsage: false)时,虽然确实把locator中的服务替换成了返回false的新Mock,但sut早就初始化完成,持有的还是最开始那个返回true的旧Mock实例,根本不会用到你新注册的服务
  2. 如果新创建的Mock实例调用后也返回true,优先排查两个点:
    • 确认SharedPreferencesService里的firstTimeUsingApp是异步方法还是getter:如果实际是getter,你写when(() => service.firstTimeUsingApp())(带方法括号)属于stub了不存在的方法,实际调用时走的是Mock的默认返回逻辑
    • 确认removeRegistrationIfExists方法的实现是否真的正确移除了旧的类型注册:如果移除失败且GetIt没有开启allowReassignment = true,注册新实例时会直接抛异常;如果没抛异常却返回旧实例,要检查自定义移除逻辑的类型判断是否正确。
修复方案

按可维护性优先级推荐:

  1. 改用构造函数注入(最推荐,从根源避免定位器时序问题)
    不要在类内部直接通过locator拿依赖,改成通过构造参数传入,测试时可以手动传入配置好的Mock实例,完全不需要反复注册/反注册locator里的服务,也不会出现实例引用不一致的问题:
    class LoginAndUserService {
      final SharedPreferencesService _prefs;
      // 构造函数显式注入依赖
      LoginAndUserService({required SharedPreferencesService prefs}) : _prefs = prefs;
    }
    
    测试时直接传入对应Mock即可:
    setUp(() {
      registerServices();
      // 初始化sut时手动传入依赖实例
      sut = LoginAndUserService(prefs: locator<SharedPreferencesService>());
    });
    
    test('loadAuthenticationPage - firstTimeUsingApp set to value in sharedprefs', () async {
      // 先创建Mock并配置好返回值
      final mockPrefs = SharedPreferencesServiceMock();
      when(() => mockPrefs.firstTimeUsingApp()).thenAnswer((_) async => false);
      when(() => mockPrefs.firstTimeAppUsageComplete()).thenAnswer((_) async => true);
      // 重新初始化sut,传入配置好的Mock
      sut = LoginAndUserService(prefs: mockPrefs);
    
      // 后续执行测试逻辑
      await sut.loadAuthenticationPage();
      final result = sut.firstTimeUsingApp;
      expect(result, false);
    });
    
  2. 保留现有locator直接获取写法的修复方式
    如果不想改现有服务层的代码,那你在测试用例里重新注册完Mock服务之后,必须重新初始化sut实例,让它从locator重新拉取新的Mock引用:
    test('loadAuthenticationPage - firstTimeUsingApp set to value in sharedprefs', () async {
      // ARRANGE
      final service = getAndRegisterSharedPreferencesServiceMock(firstTimeUsage: false);
      // 重新初始化sut,拿到新注册的Mock服务引用
      sut = LoginAndUserService();
    
      // ACT
      await sut.loadAuthenticationPage();
      final firstTimeUsingAppValue = await service.firstTimeUsingApp();
      print('SharedPrefs firstTimeUsingApp is $firstTimeUsingAppValue');
      final result = sut.firstTimeUsingApp;
    
      // ASSERT
      expect(result, false);
    });
    
  3. 适配getter场景的stub修正:
    如果firstTimeUsingApp实际是getter而非方法,要去掉stub里的方法括号:
    // getter对应的stub写法
    when(() => service.firstTimeUsingApp).thenAnswer((_) async => false);
    
排查方向

如果修改后还是不符合预期,按顺序排查:

  • 给getAndRegisterSharedPreferencesServiceMock加日志打印每次创建的Mock实例hashCode,同时在LoginAndUserService构造函数里打印_prefs的hashCode,确认sut持有的实例和你配置返回值的实例是同一个
  • 检查loadAuthenticationPage方法的内部逻辑,确认它确实调用了_prefs.firstTimeUsingApp(),且正确把返回值赋值给了sut.firstTimeUsingApp字段,没有提前缓存旧值
  • 所有测试执行前调用locator.reset()重置定位器的所有注册,避免不同测试用例之间的注册状态互相污染

内容的提问来源于stack exchange,提问作者Charlie Page

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 00:57:19