Flutter Widget测试:ViewModel回调Stub失效,如何触发变量切换?
解决Flutter Widget测试中Mockito Stub回调未执行的问题
问题描述
你需要为以下InkWell组件编写Widget测试:
InkWell( key: const Key(kDraftTileKey), onTap: () { context.read<CreatorHubTapViewModel>().startPublishingFlowAgain(startPublishingFlowAgainPressed: startPublishingFlowAgainPressed); }, child:... );
点击该组件时会调用如下函数:
void startPublishingFlowAgainPressed() { widget.viewModel.saveNFT(nft: widget.nft); Navigator.of(context).pushNamed(RouteUtil.kRouteHome); }
你已经用Mockito创建了CreatorHubTapViewModel的Mock类:
class CreatorHubTapViewModel extends ChangeNotifier { void onViewOnPylons({required VoidCallback onViewOnPylonsPressed}) { onViewOnPylonsPressed.call(); } void startPublishingFlowAgain({required VoidCallback startPublishingFlowAgainPressed}) { startPublishingFlowAgainPressed.call(); } }
当前测试中,你尝试StubstartPublishingFlowAgain方法,希望触发回调将clicked变量设为true,但Stub的thenAnswer和回调本身都未执行:
when(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: () { clicked = true; })).thenAnswer((realInvocation) {});
请问如何实现目标,即在该函数调用时切换测试变量?
问题原因
你当前的Stub写法用了具体的回调实例作为参数匹配条件,但Widget实际传入的startPublishingFlowAgainPressed是另一个完全不同的回调实例,Mockito无法匹配到对应的调用,所以Stub逻辑不会执行。
解决方案
使用Mockito的any匹配器来匹配回调参数,然后在thenAnswer中处理变量或执行真实回调:
方法1:执行真实回调并设置测试变量
如果需要保留原回调的逻辑(比如saveNFT调用和路由跳转),可以在Stub中获取真实传入的回调并执行,同时修改测试变量:
bool clicked = false; // 使用anyNamed匹配命名参数 when(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: anyNamed('startPublishingFlowAgainPressed'))) .thenAnswer((invocation) { // 获取真实调用传入的回调并执行 final callback = invocation.namedArguments[Symbol('startPublishingFlowAgainPressed')] as VoidCallback; callback(); // 设置测试变量 clicked = true; }); // 触发InkWell点击 await tester.tap(find.byKey(const Key(kDraftTileKey))); await tester.pumpAndSettle(); // 验证变量状态 expect(clicked, isTrue); // 验证ViewModel方法是否被调用 verify(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: anyNamed('startPublishingFlowAgainPressed'))).called(1);
方法2:直接在Stub中修改变量(无需执行原回调)
如果只需要验证方法被调用,不需要执行原回调的业务逻辑,可以直接在Stub里修改测试变量:
bool clicked = false; when(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: anyNamed('startPublishingFlowAgainPressed'))) .thenAnswer((_) { clicked = true; }); // 触发点击并验证 await tester.tap(find.byKey(const Key(kDraftTileKey))); await tester.pumpAndSettle(); expect(clicked, isTrue); verify(viewModel.startPublishingFlowAgain(startPublishingFlowAgainPressed: anyNamed('startPublishingFlowAgainPressed'))).called(1);
额外验证逻辑(可选)
如果需要验证回调内的saveNFT调用和路由跳转,可以单独对这些逻辑做测试:
// 对saveNFT方法做Stub when(viewModel.saveNFT(nft: anyNamed('nft'))).thenAnswer((_) {}); // 触发点击 await tester.tap(find.byKey(const Key(kDraftTileKey))); await tester.pumpAndSettle(); // 验证saveNFT被调用 verify(viewModel.saveNFT(nft: anyNamed('nft'))).called(1); // 验证路由跳转(示例:检查是否跳转到Home页面) expect(find.byType(HomeScreen), findsOneWidget);
内容的提问来源于stack exchange,提问作者Ahmad hassan
相关产品推荐
相关产品推荐

