如何用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); }); });
NavigationService代码(基于FilledStacked架构)
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:提取谓词为可复用实例
将谓词从匿名闭包改为类成员变量,让生产代码和测试代码使用同一个实例,这样就能直接匹配验证:
- 修改ViewModel代码,抽离谓词:
class CurrentViewModel { // 抽离谓词为类成员 final RoutePredicate homeRoutePredicate = (route) => route.settings.name == '/home-view'; void popUntilHomeView() { navigationService.popUntil(homeRoutePredicate); } }
- 在测试中使用该实例进行验证:
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
相关产品推荐
相关产品推荐

