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

Flutter用Mockito写弹窗测试用例遇空类型错误及全覆盖求助

Flutter Mockito测试空类型错误与全量代码覆盖解决方案

问题概述

测试handleCaregiverRouteforWeb方法时触发type 'Null' is not a subtype of type 'Widget'错误,已确认context和localizer不为空但问题仍存在;同时需要覆盖该方法最后3行的导航与埋点逻辑,实现全方法代码覆盖。

错误原因分析

从报错栈可以定位到,错误起源于getUserProfileAppOnboardingCompleted方法调用时,依赖MediaQuery.of(context)获取屏幕信息,但MockBuildContext默认没有关联任何Widget树,导致MediaQuery.of返回null,进而引发空类型转换错误。另外测试代码存在逻辑矛盾:设置featureFlagService.hasAccess(FeatureFlag.BrazeIAMEvent)返回false,却又验证mockBehaviorTrackerService.track(any)被调用,这会导致测试失败。

解决方案

一、修复空类型错误

  1. Mock MediaQuery依赖
    因为getUserProfileAppOnboardingCompleted内部通过MediaQuery.of获取屏幕尺寸,需要给MockBuildContext模拟MediaQuery环境,避免真实Widget依赖:
    when(MediaQuery.of(mockBuildContext)).thenReturn(MediaQueryData(size: Size(1920, 1080)));
    
  2. 修正测试逻辑矛盾
    当设置featureFlagService.hasAccess返回false时,移除verify(mockBehaviorTrackerService.track(any));;若要测试埋点逻辑,单独编写FeatureFlag为true的测试用例。

二、实现全量代码覆盖

需要编写3个测试用例覆盖所有分支场景:

  • 场景1:用户是Caregiver + FeatureFlag关闭(覆盖弹窗、导航逻辑)
  • 场景2:用户是Caregiver + FeatureFlag开启(覆盖弹窗、导航、埋点逻辑)
  • 场景3:用户非Caregiver(覆盖不弹窗、导航逻辑)

完整测试代码示例

测试用例1:Caregiver用户 + FeatureFlag关闭

test('handleCaregiverRouteforWeb updates status, shows dialog and navigates when caregiver (FeatureFlag off)', () async {
  final mockBuildContext = MockBuildContext();
  final testLocalizer = TestLocalizer();

  final viewModel = DeviceVerificationScreenViewModel(
    testLocalizer,
    context: mockBuildContext,
    memberInformation: memberInformationMinusPhone,
  );

  // Mock MediaQuery避免空错误
  when(MediaQuery.of(mockBuildContext)).thenReturn(MediaQueryData(size: Size(1920, 1080)));
  
  when(mockUserService.currentUser).thenReturn(caregiverMock);
  when(mockNavigationService.goToRoute(HomeRoute)).thenAnswer((_) async => null);
  when(mockFeatureFlagService.hasAccess(FeatureFlag.BrazeIAMEvent)).thenReturn(false);
  
  // Mock showDialogPopup(用anyNamed匹配builder参数,避免精确匹配Widget实例的问题)
  when(showDialogPopup(
    context: mockBuildContext,
    barrierDismissible: false,
    builder: anyNamed('builder'),
  )).thenAnswer((_) async {});

  await viewModel.handleCaregiverRouteforWeb();

  verify(mockUserService.updateWebUserLoggedInStatus()).called(1);
  verify(showDialogPopup(
    context: mockBuildContext,
    barrierDismissible: false,
    builder: anyNamed('builder'),
  )).called(1);
  verify(mockNavigationService.goToRoute(HomeRoute)).called(1);
  verifyNever(mockBehaviorTrackerService.track(any));
});

测试用例2:Caregiver用户 + FeatureFlag开启

test('handleCaregiverRouteforWeb tracks event when FeatureFlag is on', () async {
  final mockBuildContext = MockBuildContext();
  final testLocalizer = TestLocalizer();

  final viewModel = DeviceVerificationScreenViewModel(
    testLocalizer,
    context: mockBuildContext,
    memberInformation: memberInformationMinusPhone,
  );

  when(MediaQuery.of(mockBuildContext)).thenReturn(MediaQueryData(size: Size(1920, 1080)));
  
  when(mockUserService.currentUser).thenReturn(caregiverMock);
  when(mockNavigationService.goToRoute(HomeRoute)).thenAnswer((_) async => null);
  when(mockFeatureFlagService.hasAccess(FeatureFlag.BrazeIAMEvent)).thenReturn(true);
  
  when(showDialogPopup(
    context: mockBuildContext,
    barrierDismissible: false,
    builder: anyNamed('builder'),
  )).thenAnswer((_) async {});

  await viewModel.handleCaregiverRouteforWeb();

  verify(mockBehaviorTrackerService.track(getUserProfileAppOnboardingCompleted(mockBuildContext))).called(1);
});

测试用例3:非Caregiver用户

test('handleCaregiverRouteforWeb skips dialog when not caregiver', () async {
  final mockBuildContext = MockBuildContext();
  final testLocalizer = TestLocalizer();

  final viewModel = DeviceVerificationScreenViewModel(
    testLocalizer,
    context: mockBuildContext,
    memberInformation: memberInformationMinusPhone,
  );

  when(MediaQuery.of(mockBuildContext)).thenReturn(MediaQueryData(size: Size(1920, 1080)));
  
  when(mockUserService.currentUser).thenReturn(nonCaregiverMock); // 传入非护理人员用户实例
  when(mockNavigationService.goToRoute(HomeRoute)).thenAnswer((_) async => null);
  when(mockFeatureFlagService.hasAccess(FeatureFlag.BrazeIAMEvent)).thenReturn(false);

  await viewModel.handleCaregiverRouteforWeb();

  verifyNever(showDialogPopup(
    context: mockBuildContext,
    barrierDismissible: false,
    builder: anyNamed('builder'),
  ));
  verify(mockNavigationService.goToRoute(HomeRoute)).called(1);
});

内容的提问来源于stack exchange,提问作者Nithyashree B L

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 05:45:58