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的外部依赖,测试时直接主动触发轮询回调即可,稳定性更高:
- 首先定义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()); }); // 原有状态发射逻辑不变 } }
- 测试时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
相关产品推荐
相关产品推荐

