调用longPress()时Widget测试失败:无法找到可命中测试的目标组件
问题现象
执行Widget测试调用longPress()方法时触发报错:
The finder "zero widgets with key [<'owner_view_key'>] (considering only hit-testable ones)
(ignoring offstage widgets)" (used in a call to "longPress()") could not find any matching widgets.
代码背景
测试代码
testWidgets('test case for gestures', (tester) async { await tester.setScreenSize(); await tester.testAppForWidgetTesting(OwnerView( nft: MOCK_NFT, ownerViewViewModel: viewModel, )); await tester.pumpAndSettle(); final gestureDetectorOwnerScreen = find.byKey(const ValueKey(kOwnerViewKeyValue)); await tester.ensureVisible(gestureDetectorOwnerScreen); await tester.pumpAndSettle(const Duration(seconds: 5)); await tester.longPress(gestureDetectorOwnerScreen.hitTestable()); await tester.pumpAndSettle(); });
组件代码
return Scaffold( backgroundColor: kBlack, body: GesturesForDetailsScreen( key: const ValueKey(kOwnerViewKeyValue), screen: DetailScreen.ownerScreen, viewModel: viewModel, nft: widget.nft, child:Container(...), ), );
GesturesForDetailsScreen实现
return GestureDetector( onTapUp: widget.tapUp, onHorizontalDragUpdate: widget.screen == DetailScreen.ownerScreen ? _onHorizontalDragUpdate : null, onHorizontalDragEnd: widget.screen == DetailScreen.ownerScreen ? _onHorizontalDragEnd : null, onHorizontalDragStart: widget.screen == DetailScreen.ownerScreen ? _onHorizontalDragStart : null, onVerticalDragUpdate: _onVerticalDragUpdate, onVerticalDragEnd: _onVerticalDragEnd, onVerticalDragStart: _onVerticalDragStart, onLongPressStart: _onLongPressStart, onLongPressEnd: _onLongPressEnd, child: widget.child, );
问题原因
当前ValueKey(kOwnerViewKeyValue)设置在**自定义容器组件GesturesForDetailsScreen**上,而非实际处理手势的内部GestureDetector。hitTestable()仅识别本身具备命中测试能力的Widget(如GestureDetector、InkWell等触摸组件),外层容器组件本身不响应触摸事件,因此通过该key查找并调用hitTestable()会找不到匹配的可触摸Widget。
解决方案
方案1:将key直接绑定到内部GestureDetector
修改GesturesForDetailsScreen的实现,把外部传入的key传递给内部的GestureDetector:
// 修改GesturesForDetailsScreen的参数,添加key接收 class GesturesForDetailsScreen extends StatelessWidget { final Key? key; // 新增key参数 // ...其他参数 const GesturesForDetailsScreen({ super.key, // 接收外部key // ...其他参数 }); @override Widget build(BuildContext context) { return GestureDetector( key: key, // 将外部key绑定到GestureDetector onTapUp: widget.tapUp, // ...其他手势回调 child: widget.child, ); } }
测试代码无需修改,即可正常找到可命中的Widget。
方案2:测试中通过后代查找定位GestureDetector
如果不想修改组件结构,可在测试代码中通过find.descendant定位外层组件内部的GestureDetector:
testWidgets('test case for gestures', (tester) async { await tester.setScreenSize(); await tester.testAppForWidgetTesting(OwnerView( nft: MOCK_NFT, ownerViewViewModel: viewModel, )); await tester.pumpAndSettle(); // 先找到外层组件,再查找其内部的GestureDetector final outerWidget = find.byKey(const ValueKey(kOwnerViewKeyValue)); final gestureDetector = find.descendant( of: outerWidget, matching: find.byType(GestureDetector), ); await tester.ensureVisible(gestureDetector); await tester.pumpAndSettle(); await tester.longPress(gestureDetector); await tester.pumpAndSettle(); });
方案3:让GesturesForDetailsScreen本身成为可触摸组件
将GesturesForDetailsScreen直接继承自GestureDetector,而非作为容器包裹它,这样外层组件本身就是可命中的触摸组件:
class GesturesForDetailsScreen extends GestureDetector { GesturesForDetailsScreen({ super.key, required DetailScreen screen, required ViewModel viewModel, required NFT nft, required Widget child, }) : super( onTapUp: widget.tapUp, onHorizontalDragUpdate: screen == DetailScreen.ownerScreen ? _onHorizontalDragUpdate : null, // ...其他手势回调 child: child, ); // ...手势处理方法 }
内容的提问来源于stack exchange,提问作者Ahmad hassan

