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

如何测试addPostFrameCallback回调内emit状态的bloc/cubit

方案1:在testWidgets中手动监听Cubit状态流(推荐,更贴合真实业务场景)

blocTest的状态校验本质是监听Cubit的stream属性收集emit的状态,你完全可以在testWidgets环境中自己实现这个逻辑,同时借助tester的帧调度能力触发addPostFrameCallback:

testWidgets(
  'constructor emits chatIsLoading: false after scroll to bottom completes',
  (WidgetTester tester) async {
    // 初始化ScrollController并挂载到真实ListView,模拟业务场景的滚动环境
    final scrollController = ScrollController();
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: ListView(
            controller: scrollController,
            // 生成足够多的列表项模拟真实聊天列表的滚动高度
            children: List.generate(100, (index) => Text('消息$index')).toList(),
          ),
        ),
      ),
    );

    final cubit = ChatCubit(scrollController);
    // 收集所有emit的状态
    final emittedStates = <ChatState>[];
    final streamSub = cubit.stream.listen(emittedStates.add);

    // 触发帧调度,等待所有postFrameCallback、递归滚动逻辑执行完成
    await tester.pumpAndSettle();

    // 校验状态是否符合预期
    expect(emittedStates, [ChatState(chatIsLoading: false)]);

    // 测试资源释放
    await streamSub.cancel();
    cubit.close();
    scrollController.dispose();
  },
);

方案2:在blocTest中手动调度帧(适合单元测试,运行速度更快)

如果不需要真实的页面渲染,也可以在blocTest中初始化WidgetBinding,手动触发帧回调执行你的逻辑:

import 'package:mockito/mockito.dart';

blocTest<ChatCubit, ChatState>(
  'constructor emits chatIsLoading: false after frame callback runs',
  // 初始化WidgetBinding获取帧调度能力
  setUp: () => WidgetsFlutterBinding.ensureInitialized(),
  build: () {
    // 用Mockito打桩ScrollController及其依赖,避免依赖真实渲染环境
    final mockScrollController = MockScrollController();
    when(mockScrollController.hasClients).thenReturn(true);
    when(mockScrollController.position).thenReturn(
      ScrollPosition.withSingleContext(
        physics: const AlwaysScrollableScrollPhysics(),
        context: MockScrollContext(),
        initialPixels: 0,
        keepScrollOffset: true,
        oldPosition: null,
      ),
    );
    when(mockScrollController.jumpTo(any)).thenReturn(null);
    return ChatCubit(mockScrollController);
  },
  act: (cubit) async {
    // 手动触发帧执行postFrameCallback
    WidgetsBinding.instance.handleBeginFrame(Duration.zero);
    WidgetsBinding.instance.handleDrawFrame();
    // 等待递归滚动逻辑执行完成
    await Future.doWhile(() => Future.delayed(Duration.zero, () => cubit.state.chatIsLoading));
  },
  expect: () => [ChatState(chatIsLoading: false)],
);

内容的提问来源于stack exchange,提问作者Vega180

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 16:36:04