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

Flutter使用Mockito测试时FutureBuilder Snapshot为null如何解决

问题解决方法

根因分析

你遇到的问题主要由以下几个错误导致:

  1. 测试执行逻辑错误:调用tester.pumpWidget后直接执行断言,没有等待异步Future执行完成,此时Future处于pending状态,snapshot.hasData自然为false。
  2. FutureBuilder逻辑冗余:拿到异步快照数据后没有直接使用,反而额外将Future转为Stream监听赋值给实例变量,导致数据更新时序异常。
  3. 组件查找方式错误:使用find.byWidget匹配组件,该方法要求组件实例完全相等,你测试中单独构造的ProductBox和页面渲染生成的ProductBox不是同一个实例,就算数据正确也无法匹配到。
  4. 组件状态规范错误:在StatelessWidget中定义可变的实例变量存储列表数据,不符合无状态组件的设计规范,组件重绘时会导致数据丢失。

修复方案

1. 业务代码修改

调整FutureBuilder的逻辑,直接使用snapshot.data返回的结果,不要额外存储可变实例变量:

Expanded(
  child: FutureBuilder<List<ProductBoxModel>>(
    future: lastSearchFuture,
    builder: (context, snapshot){
      // 先判断异步状态是否完成,再判断是否有数据
      if(snapshot.connectionState == ConnectionState.done && snapshot.hasData){
        final lastSearch = snapshot.data!;
        return Padding(
          padding: sidePadding,
          child: Container(
            width: size.width,
            height: size.height,
            child: ListView.builder(
              itemCount: lastSearch.length,
              itemBuilder: (context, index){
                return ProductBox(
                  productBoxModel: lastSearch[index],
                );
              },
            ),
          )
        );
      } else if (snapshot.hasError) {
        // 可选添加错误处理逻辑
        return const Text('数据加载失败');
      } else {
        return Container(
          alignment: Alignment.center,
          width: size.width,
          height: size.height,
          child: const CircularProgressIndicator(),
        );
      }
    }
  )
),

如果有同时等待多个Future的需求,可以使用Future.wait合并两个请求的Future,传给FutureBuilder即可。

2. 测试代码修改

首先补充缺失的queryParameters定义,修改组件查找逻辑,pumpWidget后添加等待异步任务完成的逻辑:

@GenerateMocks([http.Client])
void main(){
  group('Landing Screen', (){
    testWidgets('When get to server endpoint, then LandingScreen should shows ProductBox', (WidgetTester tester) async {
      await tester.runAsync(() async {
        final client = MockClient();
        // 补充queryParameters定义,和接口请求参数保持一致
        const queryParameters = {'userId': '5'};
        final product =  Product(id: "a1838sn18f", name: "Sabão em pó", description:"Tests",supplierItemCode: "35", unityMeasurement: "kg", unityMeasurementQuantity: 5, image:"https://a-static.mlcdn.com.br/1500x1500/sabao-em-pedra-ype-5-unidades-ype/costaatacado/90131/fd9f6b24567bc11702c189709a9f8dd3.jpg" );
        final supplier = Supplier(fantasyName: "aisdiaj", companyName: "TEste s1", CNPJ: "5575554");
        // 修改组件查找方式,改为匹配类型,也可以根据组件属性比如商品名称查找
        final findProductBox = find.byType(ProductBox);

        when(client
            .get(Uri.http(DOMAIN, LAST_SEARCH_URL, queryParameters)))
            .thenAnswer((_) async =>
            http.Response('[{"product":{"id":"a1838sn18f","createdByUserId":"fake","createdDate":null","lastModifiedByUserId":null","lastModifiedDate":null","name":"Sabão em pó","description":"Tests","supplierItemCode":"35","unityMeasurement":"kg","unityMeasurementQuantity":5,"image":"https://a-static.mlcdn.com.br/1500x1500/sabao-em-pedra-ype-5-unidades-ype/costaatacado/90131/fd9f6b24567bc11702c189709a9f8dd3.jpg"}'
                ',"supplier":{"id":"asdle2","createdByUserId":null,"createdDate":null,"lastModifiedByUserId":null,"lastModifiedDate":null,"fantasyName":"aisdiaj","companyName":"TEste s1","cnpj":"5575554"}}]'
                , 200));

        when(client
            .get(Uri.http(DOMAIN, MOST_SEARCH_URL, queryParameters)))
            .thenAnswer((_) async =>
            http.Response('[{"product":{"id":"a1838sn18f","createdByUserId":"fake","createdDate":null","lastModifiedByUserId":null","lastModifiedDate":null","name":"Sabão em pó","description":"Tests","supplierItemCode":"35","unityMeasurement":"kg","unityMeasurementQuantity":5,"image":"https://a-static.mlcdn.com.br/1500x1500/sabao-em-pedra-ype-5-unidades-ype/costaatacado/90131/fd9f6b24567bc11702c189709a9f8dd3.jpg"}'
                ',"supplier":{"id":"asdle2","createdByUserId":null,"createdDate":null,"lastModifiedByUserId":null,"lastModifiedDate":null,"fantasyName":"aisdiaj","companyName":"TEste s1","cnpj":"5575554"}}]'
                , 200));

        await tester.pumpWidget(MaterialApp(home: LandingScreen(client),));
        // 等待所有异步任务执行完成、页面渲染结束
        await tester.pumpAndSettle();

        expect(findProductBox, findsWidgets);
      });
    });
  });
}

修改完成后即可正常等待接口返回数据后再执行断言,测试可以正常通过。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 01:54:03