Flutter Widget测试无法找到视图切换生成的目标Widget问题
问题背景
页面实现了两种数据展示视图的切换能力:
- 第一种为ListView列表视图
- 第二种为GridView网格视图,通过两个专属图标触发视图切换操作
视图切换图标效果如下:
切换图标实现代码:
Padding changeView() { return Padding( padding: const EdgeInsets.only(right: 15.0), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Padding( padding: const EdgeInsets.all(5.0), child: GestureDetector( key: const Key("listViewIcon"), child: const Icon(conf.listView), onTap: () => setState(() { _isListView = true; }), ), ), Padding( padding: const EdgeInsets.all(5.0), child: GestureDetector( key: const Key("gridViewIcon"), child: const Icon(conf.gridView), onTap: () => setState(() { _isListView = false; }), ), ), ], ), ); }
点击图标时会更新_isListView状态属性,组件build阶段会根据该属性值判断渲染GridView还是ListView,build方法实现代码如下:
Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Column( children: [ //搜索和筛选区域 searchAndFilter(context), const Divider(), //视图切换按钮 changeView(), //内容列表区域 _isListView ? listView(TEST.objects()) : gridView(TEST.objects()), ], ), ); }
遇到的问题
编写Widget测试验证切换逻辑时抛出异常,测试代码如下:
void main() { List<ObjectView> objects = []; ObjectView o1 = ObjectView(); o1.image = const AssetImage('assets/avatar.jpeg'); o1.title = "TITLE"; o1.payload = "test"; objects.add(o1); testWidgets("View Change Test", (WidgetTester tester) async { //相关组件定位器 final listViewIcon = find.byKey(const Key("listViewIcon")); final gridViewIcon = find.byKey(const Key("gridViewIcon")); await tester.pumpWidget( const MaterialApp( home: HomePage(title: conf.appName), ), ); await tester.tap(listViewIcon); await tester.pump(); expect(find.byWidget(ListViewScroll(objects: objects)), findsOneWidget); }); }
预期点击图标后页面会构建对应的ListViewScroll或GridViewScroll组件,但测试运行报错提示找不到目标组件,错误截图如下:
附ListViewScroll组件实现代码:
class ListViewScroll extends StatelessWidget { const ListViewScroll({ Key? key, required this.objects, }) : super(key: key); final List<ObjectView> objects; @override Widget build(BuildContext context) { return ListView.builder( itemCount: objects.length, itemBuilder: (BuildContext context, int index) { return SizedBox( height: conf.listTileHeight, child: Card( child: Center( child: ListTile( leading: Image( image: objects.elementAt(index).image!, ), title: Text(objects.elementAt(index).title!), subtitle: Text(objects.elementAt(index).payload!), ), ), ), ); }, ); } }
问题原因
测试找不到目标组件是两个核心问题导致的:
- 组件匹配逻辑不成立:
find.byWidget默认通过组件的深度相等判断匹配目标,测试代码里手动新建了一个objects列表,而页面实际渲染ListViewScroll时传入的是TEST.objects()返回的列表实例。Dart中List的默认相等判断是引用相等,两个列表哪怕内容完全一致,只要不是同一个实例,就会被判定为不相等,自然匹配不到页面上真实渲染的组件。 - 渲染等待不充分+初始状态缺失:代码中没有给
_isListView设置明确初始值,首次渲染状态不可控;点击图标后只调用了一次tester.pump(),如果组件存在渲染动画、异步加载逻辑,单次帧刷新无法等待组件完全挂载完成。
修复方案
按以下步骤调整即可解决问题:
- 给状态变量
_isListView设置明确初始值,比如默认展示网格视图就写bool _isListView = false;,避免初始渲染状态不确定。 - 调整测试中的组件匹配逻辑,不要手动构造新的组件实例做匹配,优先选两种更稳定的匹配方式:
- 方式一:给
ListViewScroll和GridViewScroll加上固定的Key,渲染时传入Key,测试时直接通过Key查找,不受数据源参数影响:// 组件渲染时加Key _isListView ? listView(key: const Key("listViewScroll"), objects: TEST.objects()) : gridView(key: const Key("gridViewScroll"), objects: TEST.objects()), // 测试断言 expect(find.byKey(const Key("listViewScroll")), findsOneWidget); - 方式二:直接按组件类型查找,不需要比对构造参数:
expect(find.byType(ListViewScroll), findsOneWidget);
- 方式一:给
- 点击操作后替换
tester.pump()为tester.pumpAndSettle(),等待所有帧渲染、动画执行完成后再做断言,避免组件还没挂载就执行校验。
如果确实需要校验传入列表的内容正确性,可以在测试中直接读取TEST.objects()作为校验数据源,不要自己新建列表实例。
内容的提问来源于stack exchange,提问作者aedemirsen
相关产品推荐
相关产品推荐

