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

升级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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 02:57:20