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

如何用Mockito验证Flutter NavigationService的popUntil谓词调用?

Flutter单元测试:验证popUntil传入的谓词是否正确

问题场景

我正在为Flutter应用编写单元测试,目标是验证popUntilHomeView函数是否能正确调用NavigationService的popUntil方法,传入匹配/home-view路由的谓词。

测试代码

group('popUntilHomeView - ', () {
  test(
      'when popUntilHomeView is called the application should pop the stack until reaching the home screen',
    () {
  // Arrange
  final navigationService = getAndRegisterNavigationServiceMock();
  final viewModel = CurrentViewModel();
  // Act
  viewModel.popUntilHomeView();
  // Assert
  verify(navigationService.popUntil((route) => route.settings.name == 
  '/home-view')).called(1);
  });
});
void popUntil(RoutePredicate predicate, {int? id}) {
   G.Get.until(predicate, id: id);
}

测试失败结果

运行测试后提示无匹配调用,实际调用的是闭包:

No matching calls. All calls: MockNavigationService.popUntil(Closure: (Route) => bool, {id: null})
(If you called `verify(...).called(0);`, please instead use `verifyNever(...);`.)

Mockito生成的Mock代码

@override
 void popUntil(
 _i8.RoutePredicate? predicate, {
 int? id,
 }) =>
   super.noSuchMethod(
     Invocation.method(
       #popUntil,
       [predicate],
       {#id: id},
      ),
      returnValueForMissingStub: null,
    );

核心问题

如何验证NavigationService的popUntil方法是否传入了逻辑正确的谓词?

解决方案

方案1:提取谓词为可复用实例

将谓词从匿名闭包改为类成员变量,让生产代码和测试代码使用同一个实例,这样就能直接匹配验证:

  1. 修改ViewModel代码,抽离谓词:
class CurrentViewModel {
  // 抽离谓词为类成员
  final RoutePredicate homeRoutePredicate = (route) => route.settings.name == '/home-view';

  void popUntilHomeView() {
    navigationService.popUntil(homeRoutePredicate);
  }
}
  1. 在测试中使用该实例进行验证:
verify(navigationService.popUntil(viewModel.homeRoutePredicate)).called(1);

方案2:捕获谓词并手动验证逻辑

如果无法抽离谓词,可使用Mockito的captureAny捕获传入的谓词,然后手动测试它的逻辑是否符合预期:

group('popUntilHomeView - ', () {
  test(
      'when popUntilHomeView is called the application should pop the stack until reaching the home screen',
    () {
  // Arrange
  final navigationService = getAndRegisterNavigationServiceMock();
  final viewModel = CurrentViewModel();
  // Act
  viewModel.popUntilHomeView();
  // Assert
  // 捕获传入的谓词
  final capturedPredicate = verify(navigationService.popUntil(captureAny)).captured.first as RoutePredicate;
  
  // 测试谓词对目标路由返回true
  final homeRoute = MaterialPageRoute(settings: const RouteSettings(name: '/home-view'));
  expect(capturedPredicate(homeRoute), isTrue);
  
  // 测试谓词对非目标路由返回false
  final otherRoute = MaterialPageRoute(settings: const RouteSettings(name: '/other-view'));
  expect(capturedPredicate(otherRoute), isFalse);
  });
});

注:captureAny可以匹配非空参数,解决了直接用any因谓词非空无法通过的问题。

内容的提问来源于stack exchange,提问作者thenry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 16:25:12