升级flutter_bloc后mockito测试报未注册事件处理器错误
问题根因
flutter_bloc 8.0.0及以上版本已完全废弃mapEventToState API,所有事件处理器必须在Bloc构造函数内通过on<具体事件类型>方法注册,未注册的事件被触发时就会抛出Bad state: No handler registered for event异常。
具体修改方案
所有事件注册逻辑必须放在Bloc构造函数的执行体内(即构造函数冒号初始化super状态之后的大括号块中),原有mapEventToState中拆分的三类事件逻辑,分别对应到三个独立的on<Event>回调中即可,不需要保留原有mapEventToState重写方法。
参考实现代码如下:
import 'package:bloc/bloc.dart'; import 'dart:async'; // 替换为你项目中实际的事件、状态、消息仓库导入路径 import 'message_event.dart'; import 'message_state.dart'; import 'message_repository.dart'; class MessageBloc extends Bloc<MessageEvent, MessageState> { final MessageRepository messageRepository; StreamSubscription? _messageSubscription; MessageBloc({required this.messageRepository}) : super(MessageInitial()) { // 注册Subscribed事件处理器 on<Subscribed>((event, emit) async { // 先取消已有订阅避免重复监听 await _messageSubscription?.cancel(); // 监听消息仓库的数据流,收到新消息时触发内部接收事件 _messageSubscription = messageRepository.messageStream().listen( (newMessage) => add(_MessageReceived(message: newMessage)), onError: (err) { emit(MessageLoadFailure(errorMsg: err.toString())); } ); // 订阅完成后初始化消息列表状态,可根据你项目的状态定义自行调整 emit(MessageLoadSuccess(messages: const [])); }); // 注册内部私有事件_MessageReceived处理器 on<_MessageReceived>((event, emit) { if (state is MessageLoadSuccess) { final currentMsgList = (state as MessageLoadSuccess).messages; // 追加新消息后更新状态 emit(MessageLoadSuccess( messages: List.from(currentMsgList)..add(event.message) )); } }); // 注册MessageSent发送消息事件处理器 on<MessageSent>((event, emit) async { try { await messageRepository.sendTextMessage(event.content); // 发送成功无需手动更新列表,等待消息流推送新消息即可,也可根据业务加发送成功状态 } catch (e) { emit(MessageSendFailure(errorMsg: e.toString(), pendingContent: event.content)); } }); } // 关闭Bloc时先取消流订阅,防止内存泄漏 @override Future<void> close() { _messageSubscription?.cancel(); return super.close(); } }
单元测试适配要点
- 每个测试用例单独初始化MessageBloc实例,不要复用全局实例,确保实例构造时所有事件处理器完成注册
- 使用mockito打桩时,要覆盖MessageRepository中被Bloc调用的所有方法(消息流监听、发送消息方法),避免空指针
- 触发事件直接调用
bloc.add(对应事件实例)即可,不需要手动调用任何事件分发方法
参考测试代码:
import 'package:bloc_test/bloc_test.dart'; import 'package:mockito/mockito.dart'; import 'package:mockito/annotations.dart'; // 替换为你项目的实际导入路径 import 'message_bloc.dart'; import 'message_event.dart'; import 'message_state.dart'; import 'message_repository.dart'; // 替换为你自己的mock生成文件路径 import 'message_bloc_test.mocks.dart'; @GenerateMocks([MessageRepository]) void main() { late MockMessageRepository mockRepo; setUp(() { mockRepo = MockMessageRepository(); // 默认打桩消息流返回空流,可根据测试场景自定义返回值 when(mockRepo.messageStream()).thenAnswer((_) => Stream.empty()); }); blocTest<MessageBloc, MessageState>( '触发Subscribed事件后成功订阅消息流,返回加载成功状态', build: () => MessageBloc(messageRepository: mockRepo), act: (bloc) => bloc.add(Subscribed()), expect: () => [isA<MessageLoadSuccess>()], verify: (_) { verify(mockRepo.messageStream()).called(1); }, ); blocTest<MessageBloc, MessageState>( '触发MessageSent事件后调用仓库发送消息方法', build: () => MessageBloc(messageRepository: mockRepo), setUp: () { when(mockRepo.sendTextMessage(any)).thenAnswer((_) async {}); }, act: (bloc) => bloc.add(MessageSent(content: '测试消息')), verify: (_) { verify(mockRepo.sendTextMessage('测试消息')).called(1); }, ); }
常见踩坑
- 不要将
on<Event>注册逻辑放到Bloc的其他自定义方法中,必须放在构造函数内,否则实例化时不会执行注册逻辑 - 私有事件(比如
_MessageReceived)也需要完成注册,否则内部触发时同样会报未注册错误 - 注册事件时不要省略事件的完整类型(包含泛型),否则Bloc无法匹配到对应处理器
- 不需要保留旧版的
mapEventToState重写方法,该方法在新版中不会被调用,留着只会产生无用代码
内容的提问来源于stack exchange,提问作者JohnBradens
相关产品推荐
相关产品推荐

