Flutter blocTest单元测试状态相等性对比失败问题排查
问题根因
测试失败是两个问题叠加导致的:
ViewData未实现值相等判断:HomeState虽然继承了Equatable,但作为属性的ViewData既没有继承Equatable,也没有重写==和hashCode,Dart默认的对象相等判断只校验两个实例是否是同一个内存地址的对象。测试里expect写的ViewData.loading()、ViewData.loaded()是测试代码里新创建的实例,和Bloc里创建的ViewData实例内存地址不同,哪怕属性完全一致,相等判断也会返回false,这就是报错里Expected和Actual打印内容完全相同但判断不匹配的核心原因。- 预期状态和Bloc实际发射的状态不匹配:看
_onSuccessRepositories逻辑,发射完loaded状态后,会等待500ms再发射一个isShowTitle: true的新状态,你当前测试的expect列表只写了loading、loaded两个状态,少了最后这个状态,就算解决了相等判断问题,测试依然会因为状态数量不对报错。
修复方案
1. 给ViewData补全值相等逻辑
修改ViewData代码,继承Equatable并把所有属性加入props列表,参与值比较:
import 'package:equatable/equatable.dart'; // 注意:ViewState是枚举本身支持值比较,如果你自定义的Failure、RepositoriesEntity类也需要做相等判断,同样要继承Equatable补全props class ViewData<T> extends Equatable { final ViewState status; final T? data; final String message; final Failure? failure; const ViewData._({ required this.status, this.data, this.message = "", this.failure, }); factory ViewData.loaded({T? data}) { return ViewData._(status: ViewState.loaded, data: data); } factory ViewData.error({required Failure? failure}) { return ViewData._(status: ViewState.error, failure: failure); } factory ViewData.loading({required String message}) { return ViewData._(status: ViewState.loading, message: message); } factory ViewData.initial() { return ViewData._(status: ViewState.initial); } factory ViewData.empty({required String message}) { return ViewData._(status: ViewState.empty, message: message); } @override List<Object?> get props => [status, data, message, failure]; }
2. 修正测试用例
补全Bloc实际会发射的第三个状态,同时加wait参数等待Bloc内的延迟异步任务执行完成,确保能捕获到最后一个状态:
blocTest( 'emits [loading, loaded, showTitle=true] when GetRepositories is added success', build: () { when( () => useCase.getRepositories(), ).thenAnswer((_) async => Right(repositoriesDummyEntityFromRemote)); return homeBloc; }, act: (HomeBloc bloc) => bloc.add(GetRepositories()), // 等待内部500ms延迟执行完,留100ms冗余 wait: const Duration(milliseconds: 600), expect: () => [ HomeState( statusRepositories: ViewData.loading(message: 'Loading'), isShowTitle: false, ), HomeState( statusRepositories: ViewData.loaded(data: repositoriesDummyEntityFromRemote), isShowTitle: false, ), // 补全延迟发射的显示标题状态 HomeState( statusRepositories: ViewData.loaded(data: repositoriesDummyEntityFromRemote), isShowTitle: true, ), ], );
可选替代方案(无需修改ViewData代码)
如果不想改动业务层的ViewData实现,可以按照报错提示,使用类型匹配器逐个校验状态属性,不直接比较实例:
expect: () => [ isA<HomeState>() .having((state) => state.statusRepositories.status, 'request status', ViewState.loading) .having((state) => state.isShowTitle, 'title visibility', false), isA<HomeState>() .having((state) => state.statusRepositories.status, 'request status', ViewState.loaded) .having((state) => state.statusRepositories.data, 'response data', repositoriesDummyEntityFromRemote) .having((state) => state.isShowTitle, 'title visibility', false), isA<HomeState>() .having((state) => state.statusRepositories.status, 'request status', ViewState.loaded) .having((state) => state.isShowTitle, 'title visibility', true), ],
这种方式不需要修改业务类,但长期维护成本更高,项目里所有用于状态传递的数据类、状态类统一继承Equatable是更稳妥的做法。
内容的提问来源于stack exchange,提问作者R Rifa Fauzi Komara
相关产品推荐
相关产品推荐

