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

单元测试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 called verify(...).called(0);, please instead use verifyNever(...);.)

调试确认错误根源是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;
    }
}

问题根源

  1. 测试执行顺序错误:原测试中verify调用在jobBloc.add(const JobFetch())之前,此时BLoC还未处理事件,alertBloc.add从未被调用,自然触发验证失败。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:35:01