使用 Mockito 的 FutureBuilder 快照数据为空 - Flutter

Posted

技术标签:

【中文标题】使用 Mockito 的 FutureBuilder 快照数据为空 - Flutter【英文标题】:FutureBuilder Snapshot Data is null with Mockito - Flutter 【发布时间】:2021-12-26 03:58:04 【问题描述】:

我正在为我的颤振应用程序构建一些测试,并且我发现它在调用 API 的方法返回数据之前就已经在未来的构建器上,所以快照总是空的。

如何强制 FutureBuilder 等待方法返回数据?

测试代码:

@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();

        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.byWidget(ProductBox(productBoxModel: ProductBoxModel(product: product , supplier: supplier),));


        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),));

        expect(await findProductBox, findsWidgets);

      );

    );
  );



这是 FutureBuilder:

class LandingScreen extends StatelessWidget
  http.Client client;
  
  LandingScreen(this.client);
  var lastSearch = <ProductBoxModel> [];
  var mostSearch = <ProductBoxModel> [];
  Color color = Colors.grey.withOpacity(0.5);

  @override
  Widget build(BuildContext context) 
    LandingScreenRoutes landingScreenRoutes = LandingScreenRoutes(client);
    var lastSearchFuture =  landingScreenRoutes.getLastSearch('5'); //TODO this idUser should come from logged user
    var mostSearchFuture = landingScreenRoutes.getMostSearch('5'); //TODO this idUser should come from logged user


****HIDED CONTENT****
Expanded(

                    child: FutureBuilder(
                      future: lastSearchFuture,
                      builder: (context, snapshot)
                        if(snapshot.hasData)
                          lastSearchFuture.asStream().forEach((element)  lastSearch.addAll(element););
                          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
                          return Container(
                            alignment: Alignment.center,
                            width: size.width,
                            height: size.height,
                            child: CircularProgressIndicator(),
                          );
                   )
                  ),


这是 LandingScreenRoutes 类:

const DOMAIN = "10.0.2.2:9090";
const LAST_SEARCH_URL = "/news/lastSearch";
const MOST_SEARCH_URL = "/news/mostSearch";

class LandingScreenRoutes
  http.Client client;

  LandingScreenRoutes(this.client);

  Future<List<ProductBoxModel>> getLastSearch(String userId) async 
    final queryParameters = 'userId':userId;
    var response = await client.get(Uri.http(DOMAIN, LAST_SEARCH_URL, queryParameters));
    List<ProductBoxModel> productList = [];
    for(Map<String, dynamic> p in jsonDecode(response.body))
      productList.add(ProductBoxModel.fromJson(p));
    
    return productList;
  

  Future<List<ProductBoxModel>> getMostSearch(String userId) async 
    final queryParameters = 'userId':userId;
    var response = await client.get(Uri.http(DOMAIN, MOST_SEARCH_URL, queryParameters));
    List<ProductBoxModel> productList = [];
    for(Map<String, dynamic> p in jsonDecode(response.body))
      productList.add(ProductBoxModel.fromJson(p));
    
    return productList;
  


应用程序首先检查快照是否有数据:

在它进入从后端获取数据的方法之后:

这不应该发生。因此,snapshot.hasdata 总是为假,我的测试不会通过。

如何强制应用程序等到有数据或其他东西?

【问题讨论】:

【参考方案1】:

你尝试过expectLater吗?

之前:

expect(await findProductBox, findsWidgets);

之后:

expectLater(await findProductBox, findsWidgets);

编辑: 我在这里复制了这个问题,试试:

    @GenerateMocks([http.Client])
void main() 
  group('Landing Screen', ()
    testWidgets('When get to server endpoint, then LandingScreen should shows ProductBox',

      (WidgetTester tester) async 
        final client = MockClient();
        when(client.get(any)).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: client),));

        await tester.pumpAndSettle();

        expectLater(find.byType(ProductBox, skipOffstage: false), findsWidgets);


      );

    );

在屏幕上:

  return Column(children: [
      Expanded(
          child: FutureBuilder<List<ProductBoxModel>>(
              future: LandingScreenRoutes(client).getLastSearch('5'),
              builder: (_, snapshot) 
                if (snapshot.hasData) 
                  return Padding(
                      padding: const EdgeInsets.all(16),
                      child: ListView.builder(
                        itemCount: snapshot.data!.length,
                        itemBuilder: (_, index) 
                          return ProductBox(
                            key: Key(snapshot.data![index].product.id),
                            productBoxModel: snapshot.data![index],
                          );
                        ,
                      ));
                 else 
                  return Container(
                    alignment: Alignment.center,
                    child: const CircularProgressIndicator(),
                  );
                
              ))
    ]);

你可以使用pumpAndSettle来等待所有异步方法都被解析,并且参数skipOffstage: false具有检查屏幕边界之外的项目的功能,对于列表测试非常有用,因为有些项目可能不可见

【讨论】:

同样的结果。关键是,即使我希望以后,应用程序也会落在我调用后端的方法中,因为它会检查快照是否有数据。所以我需要强制应用程序等到快照有数据或尝试其他东西。 我用可能的解决方案编辑了答案

以上是关于使用 Mockito 的 FutureBuilder 快照数据为空 - Flutter的主要内容,如果未能解决你的问题,请参考以下文章

为啥要使用 Mockito? [关闭]

使用 Mockito 测试抽象类

使用 Mockito 测试抽象类

Mockito教程

使用 mockito 验证对象属性值

mockito使用心得