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

Flutter中如何使用blocTest对Bloc内使用的Timer进行单元测试

问题本质

你当前写法的核心问题是:fakeAsync 仅包裹了事件添加和时间快进逻辑,但blocTest的expect和verify回调并不会等待fakeAsync内部触发的第二次Bloc事件处理完成,就会提前执行校验,因此只能捕获到第一次fetchResult调用。


解决方案

方案1:使用FakeAsync包裹完整测试流程

如果你希望继续用FakeAsync模拟时间,可以放弃单独用blocTest的封装,将完整测试逻辑都放在fakeAsync作用域内,确保时间快进后所有微任务执行完成再做校验:

test('repo should be called twice', () => fakeAsync((async) async {
  // 初始化测试依赖与Bloc
  final bloc = TravelBloc(
    mockRepoProvider,
    mockTrackerProvider,
  );
  // 触发第一次拉取事件
  bloc.add(const MyEvent.getStream());
  // 快进时间触发Timer回调
  async.elapse(const Duration(minutes: 1, seconds: 1));
  // 刷新所有 pending 微任务,保证Bloc处理完第二次getStream事件
  async.flushMicrotasks();

  // 执行校验
  verify(mockRepo.fetchResult).called(2);
  // 可自行补充状态校验逻辑
  expect(bloc.state, /* 你期望的状态 */);

  // 资源释放
  await bloc.close();
}));

方案2:抽离定时器依赖(更推荐)

避免模拟时间的异步同步问题,通过依赖注入将Timer创建逻辑抽离为可Mock的外部依赖,测试时直接主动触发轮询回调即可,稳定性更高:

  1. 首先定义Timer创建的抽象工厂,注入到Bloc中:
// 定义Timer工厂类型
typedef TimerFactory = Timer Function(Duration duration, VoidCallback callback);

// Bloc构造函数注入工厂,默认使用原生Timer实现
class MyBloc extends Bloc<MyEvent, MyState> {
  final TimerFactory timerFactory;
  MyBloc(this._repoProvider, this._logger, {TimerFactory? timerFactory})
    : timerFactory = timerFactory ?? Timer.new,
      super(MyState.initial()) {
    // 原有事件绑定逻辑
  }

  // 原有_myStream方法中改用注入的工厂创建Timer
  Stream<MyState> _myStream() async* {
    // 原有业务逻辑不变
    timerFactory(const Duration(minutes: 1), () {
      add(const MyEvent.getStream());
    });
    // 原有状态发射逻辑不变
  }
}
  1. 测试时Mock工厂主动触发回调,无需模拟时间:
blocTest<MyBloc, MyState>(
  "repo should be called twice",
  build: () {
    // 存储Timer回调用于手动触发
    late VoidCallback timerCallback;
    // Mock工厂实现,捕获回调
    when(mockTimerFactory(any(), any)).thenAnswer((invocation) {
      timerCallback = invocation.positionalArguments[1] as VoidCallback;
      return MockTimer(); // 只要返回符合Timer接口的Mock对象即可
    });
    return TravelBloc(
      mockRepoProvider,
      mockTrackerProvider,
      timerFactory: mockTimerFactory,
    );
  },
  act: (bloc) async {
    bloc.add(const MyEvent.getStream());
    // 手动触发Timer回调,模拟时间到期
    timerCallback();
    // 等待微任务执行完成,确保Bloc处理完第二次事件
    await Future.delayed(Duration.zero);
  },
  verify: (_) {
    verify(mockRepo.fetchResult).called(2);
  }
);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 07:36:04