Flutter自定义Widget带回调函数的测试用例执行失败求助
问题:自定义Widget测试中onObserve回调未触发的原因及修复
我实现了一个带onObserve回调的自定义Widget,当MyController检测到数据时会调用这个回调,但编写的测试用例始终失败——onObserve从未执行,导致expect(mModel.address, '5, ABC-123');断言不通过。
自定义Widget代码
class CustomWidget extends StatefulWidget { final ValueChanged<Model>? onObserve; const CustomWidget({super.key, required this.onObserve}); @override State<CustomWidget> createState() => _CustomWidgetState(); } class _CustomWidgetState extends State<CustomWidget> { @override Widget build(BuildContext context) { MyController mController = MyController(); return Stack( children: [ Center( child: SizedBox( width: 350, height: 350, child: MyObserver( controller: mController, onDetect: (code, args) { if (code.value != null) { //...TODO widget.onObserve!(myObservedData); } }), ), ), ], ); } }
测试用例代码
testWidgets('on observe', (WidgetTester tester) async { var mModel = Model(); await tester.pumpWidget(MyWrapper(Scaffold(body: CustomWidget( onObserve: (value) { value.address = '5, ABC-123'; mModel = value; }, )))); await tester.pumpAndSettle(); expect(find.byType(CustomWidget), findsOneWidget); expect(find.byType(MyObserver), findsOneWidget); expect(mModel.address, '5, ABC-123'); await tester.pumpAndSettle(); });
问题分析
1. 未触发MyController的数据检测逻辑
测试只是将Widget泵入测试环境,但没有任何操作让MyController生成数据。MyObserver的onDetect回调只有在mController检测到数据时才会执行,没有触发源自然不会走到onObserve。
2. MyController是build方法内的局部实例
在_CustomWidgetState的build方法中,每次重建都会新建MyController实例,而且测试代码无法获取这个局部的mController引用,也就没法手动触发它的检测逻辑。
3. 测试未模拟数据触发场景
当前测试流程是泵入Widget后直接断言,完全跳过了“模拟数据检测”这个关键步骤,没有给onObserve被调用的机会。
修复方案
方案1:将MyController提升为State成员变量
修改Widget的State,把MyController移到initState中初始化,成为State的成员变量,这样测试中可以获取到它的引用并触发检测:
class _CustomWidgetState extends State<CustomWidget> { late MyController mController; @override void initState() { super.initState(); mController = MyController(); } @override Widget build(BuildContext context) { return Stack( children: [ Center( child: SizedBox( width: 350, height: 350, child: MyObserver( controller: mController, onDetect: (code, args) { if (code.value != null) { widget.onObserve!(myObservedData); } }), ), ), ], ); } }
对应的测试代码修改:
testWidgets('on observe', (WidgetTester tester) async { var mModel = Model(); await tester.pumpWidget(MyWrapper(Scaffold(body: CustomWidget( onObserve: (value) { value.address = '5, ABC-123'; mModel = value; }, )))); await tester.pumpAndSettle(); // 获取CustomWidget的State,拿到Controller引用 final state = tester.state<_CustomWidgetState>(find.byType(CustomWidget)); // 触发Controller的检测逻辑(假设存在triggerDetection方法) state.mController.triggerDetection(); await tester.pumpAndSettle(); expect(find.byType(CustomWidget), findsOneWidget); expect(find.byType(MyObserver), findsOneWidget); expect(mModel.address, '5, ABC-123'); });
方案2:使用Mock模拟触发逻辑
如果无法修改Widget的实现,可以用Mock类替换MyController,直接模拟onDetect回调的触发(需要依赖mockito库):
import 'package:mockito/mockito.dart'; class MockMyController extends Mock implements MyController {} testWidgets('on observe', (WidgetTester tester) async { var mModel = Model(); await tester.pumpWidget(MyWrapper(Scaffold(body: CustomWidget( onObserve: (value) { value.address = '5, ABC-123'; mModel = value; }, )))); await tester.pumpAndSettle(); // 获取MyObserver实例,直接调用onDetect回调 final observer = tester.widget<MyObserver>(find.byType(MyObserver)); // 传入模拟的code和args(根据实际类型调整) observer.onDetect!(SomeCode(value: 'test'), null); await tester.pumpAndSettle(); expect(mModel.address, '5, ABC-123'); });
内容的提问来源于stack exchange,提问作者Ruchir
相关产品推荐
相关产品推荐

