单元测试BLoC方法时遇类型不匹配及Verify失败问题
BLoC单元测试:Mock方法未生效及验证失败问题解决
问题现象
测试BLoC的_onFetch方法时,抛出调用匹配失败错误:
No matching calls. All calls: MockAlertBloc.add(AlertError(type 'Null' is not a subtype of type 'Future
', null))
(If you calledverify(...).called(0);, please instead useverifyNever(...);.)
调试确认错误根源是jobRepository.getJobs(),但明明已经Mock了该方法,却依然触发类型错误,同时alertBloc.add的验证也不通过。
相关代码
BLoC核心方法
void _onFetch(JobFetch event, Emitter<JobState> emit) async { JobState oldState = state; try { emit(JobLoading()); List<Job> jobs = await jobRepository.getJobs(); emit(JobLoaded(jobs: jobs)); } catch (e, stacktrace) { emit(oldState); alertBloc.add(AlertError(error: e, stackTrace: stacktrace)); } }
初始测试代码
class MockJobRepository extends Mock implements JobRepository {} class MockAlertBloc extends MockBloc<AlertEvent, AlertState> implements AlertBloc {} class MockAuthBloc extends MockBloc<AuthEvent, AuthState> implements AuthBloc { @override AuthState get state => AuthLoggedIn(); } void main() { group('JobBloc', () { late JobRepository jobRepository; late AuthBloc authBloc; late AlertBloc alertBloc; late JobBloc jobBloc; setUp(() { jobRepository = MockJobRepository(); authBloc = MockAuthBloc(); alertBloc = MockAlertBloc(); jobBloc = JobBloc( jobRepository: jobRepository, authBloc: authBloc, alertBloc: alertBloc, ); }); test('adds AlertError to AlertBloc when an error occurs during JobFetch', () async { final error = Exception('Error fetching jobs'); when(() => jobRepository.getJobs()).thenThrow(error); final expectedStates = [ JobInitial(), // 旧状态 ]; final expectedAlertError = AlertError(error: error, stackTrace: null); expectLater( jobBloc.stream, emitsInOrder(expectedStates), ); verify(() => alertBloc.add(expectedAlertError)).called(1); jobBloc.add(const JobFetch()); }); }); }
JobRepository实现
class JobRepository { final JobProvider jobProvider; const JobRepository({required this.jobProvider}); Future<List<Job>> getJobs() async { List<Job> allJobs = await jobProvider.getJobs(query: {"perPage": "200"}); allJobs.retainWhere((job) { bool? isPublished = job.isPublished; return isPublished != null && isPublished && }); return allJobs; } }
问题根源
- 测试执行顺序错误:原测试中
verify调用在jobBloc.add(const JobFetch())之前,此时BLoC还未处理事件,alertBloc.add从未被调用,自然触发验证失败。 - Mock时机潜在问题:虽然
when写在事件触发前,但结合expectLater的异步监听,可能存在Mock行为未及时绑定到Mock实例的情况,导致getJobs()默认返回null,触发Null is not a subtype of Future<List>类型错误。
解决方案
方案1:调整测试逻辑顺序(更规范)
将verify移到事件触发后,并等待异步逻辑完成,同时修正预期状态(漏掉了JobLoading状态):
test('adds AlertError to AlertBloc when an error occurs during JobFetch', () async { final error = Exception('Error fetching jobs'); // 1. 先设置Mock行为 when(() => jobRepository.getJobs()).thenThrow(error); final expectedStates = [ JobLoading(), // 触发加载状态 JobInitial(), // 异常后回到初始状态 ]; // 2. 允许StackTrace为任意类型(实际捕获的不是null) final expectedAlertError = AlertError(error: error, stackTrace: isA<StackTrace>()); // 3. 先监听状态流 expectLater( jobBloc.stream, emitsInOrder(expectedStates), ); // 4. 触发BLoC事件 jobBloc.add(const JobFetch()); // 5. 等待异步操作完成后执行验证 await Future.delayed(Duration.zero); verify(() => alertBloc.add(expectedAlertError)).called(1); });
方案2:将Mock设置移到setUp中
正如你发现的,把when(() => jobRepository.getJobs()).thenThrow(error);移到setUp方法中,提前初始化Mock行为,也能解决问题:
setUp(() { jobRepository = MockJobRepository(); // 提前设置Mock行为 when(() => jobRepository.getJobs()).thenThrow(Exception('Error fetching jobs')); authBloc = MockAuthBloc(); alertBloc = MockAlertBloc(); jobBloc = JobBloc( jobRepository: jobRepository, authBloc: authBloc, alertBloc: alertBloc, ); });
关键注意事项
- 测试异步BLoC逻辑时,必须保证先设置Mock、再监听状态、触发事件、最后验证的顺序。
- 验证异步操作时,需要等待事件处理完成,可通过
await Future.delayed(Duration.zero)或者expectLater的whenComplete回调实现。 - 不要忽略BLoC触发的中间状态(比如
JobLoading),否则expectLater会因为状态不匹配而失败。
内容的提问来源于stack exchange,提问作者Kevin Trancoso
相关产品推荐
相关产品推荐

