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

StateNotifierProvider初始化异步操作引发单元测试asynchronous gap错误

问题分析与解决方案

核心问题

你遇到的两个问题本质上是同一原因导致的:

  1. MissingStubError:mock函数的设置时机晚于API调用的触发时机
  2. 异步状态更新未被正确测试:测试代码没有等待异步API调用完成,就尝试验证状态

当你调用happyPath.read(onboardingProvider)时,它会触发cardOnboardingProvider的初始化,而CardOnboardingNotifier的构造函数会立刻调用getListTypes(),进而发起getCardTypesList()请求——但此时你还没设置mock的when(...)逻辑,所以mock框架找不到匹配的 stub,抛出错误。同时,异步操作的状态更新是在Future完成后才会执行,测试代码没等待这个过程,自然看不到最终状态。


解决方案

1. 调整Mock设置顺序:先Mock,再触发Provider初始化

把mock的设置逻辑移到读取任何Provider之前,确保API调用发起时,mock已经准备好:

test("Initial value", () async {
  // 第一步:先设置好mock逻辑
  final futureListTypes = listTypesMockFromFile();
  when(happyPath.read(httpClientProvider).getCardTypesList("", {"isMock": "true"}))
      .thenAnswer((_) => futureListTypes);

  // 第二步:再读取onboardingProvider,触发cardOnboardingProvider初始化
  expect(happyPath.read(onboardingProvider), OnboardingState.showVehicleType(isNextAvailable: false));
  expect(happyPath.read(onboardingProvider.notifier).pageIndex, 0);

  // 等待mock的Future完成,确保状态更新
  await futureListTypes;

  // 验证最终状态
  expect(
    happyPath.read(cardOnboardingProvider),
    isA<_CardTypeShowCardList>()
  );
});

2. 优化Provider的异步代码(推荐)

把.then()改成async/await,代码更易读,也更便于测试和错误处理:

class CardOnboardingNotifier extends StateNotifier<CardTypesState> {
  Ref ref;
  final CardTypeListApiCalls filteringApiCalls;

  CardOnboardingNotifier({required this.ref, required this.filteringApiCalls}) 
      : super(CardTypesState.loading()) {
    getListTypes();
  }

  // 改成async函数
  Future<void> getListTypes() async {
    state = CardTypesState.loading();
    try {
      final cardList = await filteringApiCalls.getCardTypesList();
      state = CardTypesState.showCardList(cardList: cardList);
    } catch (e) {
      // 增加错误处理,测试时也能验证错误状态
      state = CardTypesState.showError();
    }
  }
}

3. 测试错误场景(可选)

如果需要测试API调用失败的情况,可以在mock中抛出异常,然后验证错误状态:

test("API call fails", () async {
  when(happyPath.read(httpClientProvider).getCardTypesList("", {"isMock": "true"}))
      .thenThrow(Exception("API error"));

  happyPath.read(onboardingProvider); // 触发初始化

  // 等待异步操作完成
  await Future.delayed(Duration.zero); // 确保微任务队列执行完毕

  expect(happyPath.read(cardOnboardingProvider), CardTypesState.showError());
});

关键注意点

  • Provider的初始化是同步过程,任何在构造函数中发起的异步调用会立刻执行,所以mock必须提前设置
  • 测试异步状态时,必须等待Future完成或者微任务队列执行完毕,才能验证最终状态
  • 始终为异步操作添加错误处理,避免未捕获的异常导致测试崩溃

内容的提问来源于stack exchange,提问作者Amin Sajadi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 07:15:35