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

StateNotifierProvider单元测试未捕获初始加载状态问题求助

StateNotifierProvider单元测试未捕获初始加载状态问题求助

我最近在给StateNotifierProvider写单元测试的时候碰到个奇怪的问题:测试失败场景时,单元测试没能捕获到初始的加载状态。Test A和Test B都正常跑通,但Test C就出问题了,想请教下大家这是怎么回事。

先贴一下我的Provider实现代码:

final paymentBundleVM = StateNotifierProvider.autoDispose<PaymentBundleVM, PaymentBundleState>(
  (ref) => PaymentBundleVM(ref.read(getPaymentBundleUseCase), ref.read(createPaymentBundleUseCase)),
  dependencies: [getPaymentBundleUseCase, createPaymentBundleUseCase],
  name: 'paymentBundleVM',
);

class PaymentBundleVM extends StateNotifier<PaymentBundleState> {
  PaymentBundleVM(this._getBundleUseCase, this._createBundleUseCase) : super(PaymentBundleLoading()) {
    unawaited(_loadDependencies());
  }

  final GetPaymentBundleUseCase _getBundleUseCase;
  final CreatePaymentBundleUseCase _createBundleUseCase;

  Future<void> _loadDependencies() async {
    try {
      final bundle = await _getBundleUseCase.run();
      if (mounted) {
        if (bundle != null)
          state = PaymentBundleLoaded(bundle);
        else
          state = PaymentBundleDisabled();
      }
    } on BaseException catch (exception) {
      if (mounted) state = PaymentBundleFailedLoadingState(exception);
    }
  }
}

abstract class PaymentBundleState extends Equatable {
  @override
  List<Object?> get props => [];
}

class PaymentBundleLoading extends PaymentBundleState {}

class PaymentBundleDisabled extends PaymentBundleState {}

class PaymentBundleLoaded extends PaymentBundleState {
  PaymentBundleLoaded(this.bundle);
  final PaymentBundle bundle;
  @override
  List<Object?> get props => [...super.props, bundle];
}

class PaymentBundleFailedLoadingState extends PaymentBundleState {
  PaymentBundleFailedLoadingState(this.exception);
  final BaseException exception;
  @override
  List<Object?> get props => [...super.props, exception];
}

然后是我的单元测试代码:

void main() {
  final getPaymentBundleUseCaseMock = GetPaymentBundleUseCaseMock();
  final createPaymentBundleUseCaseMock = CreatePaymentBundleUseCaseMock();
  late ProviderContainer container;
  final fakeBundle = PaymentBundleMock();

  setUp(() {
    container = ProviderContainer(
      overrides: [
        getPaymentBundleUseCase.overrideWith((ref) => getPaymentBundleUseCaseMock),
        createPaymentBundleUseCase.overrideWith((ref) => createPaymentBundleUseCaseMock),
      ],
    );
  });

  tearDown(() {
    reset(getPaymentBundleUseCaseMock);
    reset(createPaymentBundleUseCaseMock);
  });

  test('test A', () async {
    when(getPaymentBundleUseCaseMock.run).thenAnswer((_) => Future.value(fakeBundle));
    final listener = ListenerMock<PaymentBundleState>();
    container.listen<PaymentBundleState>(paymentBundleVM, listener, fireImmediately: true);
    await container.pump();

    verifyInOrder(
      [
        () => listener(null, PaymentBundleLoading()),
        () => listener(PaymentBundleLoading(), PaymentBundleLoaded(fakeBundle)),
      ],
    );
    verifyNoMoreInteractions(listener);
  });

  test('test B', () async {
    when(getPaymentBundleUseCaseMock.run).thenAnswer((_) => Future.value());
    final listener = ListenerMock<PaymentBundleState>();
    container.listen<PaymentBundleState>(paymentBundleVM, listener, fireImmediately: true);
    await container.pump();

    verifyInOrder(
      [
        () => listener(null, PaymentBundleLoading()),
        () => listener(PaymentBundleLoading(), PaymentBundleDisabled()),
      ],
    );
    verifyNoMoreInteractions(listener);
  });

  test('test C', () async {
    final exception = HttpException.handshake();
    when(getPaymentBundleUseCaseMock.run).thenThrow(exception);
    final listener = ListenerMock<PaymentBundleState>();
    container.listen<PaymentBundleState>(paymentBundleVM, listener, fireImmediately: true);
    await container.pump();

    verifyInOrder(
      [
        () => listener(null, PaymentBundleLoading()),
        () => listener(PaymentBundleLoading(), PaymentBundleFailedLoadingState(exception)),
      ],
    );
    verifyNoMoreInteractions(listener);
  });
}

现在的问题是,Test A和Test B都符合预期,但Test C抛出了错误:

Matching call #0 not found. All calls: ListenerMock.call(
null,
PaymentBundleFailedLoadingState( [BaseException - handshakeHttp] null.
Server message: null
User message: null
statusCode: null
))

简单说就是Test C里,测试认为状态直接从null跳到了PaymentBundleFailedLoadingState,完全没捕获到PaymentBundleLoading这个初始状态。按我的理解,状态流程应该是null -> loading -> failedLoading才对,为什么加载状态没被识别到呢?有没有大佬能帮忙分析下原因?

备注:内容来源于stack exchange,提问作者GGirotto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 12:04:36