You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 03:40:54