Reputation: 336
I am building some tests for my flutter application and I have figured out that it's getting on future builder before the method that calls the API return the data, so the snapshot always will be empty.
How can I force the FutureBuilder wait to the method return the data?
The test code:
@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);
});
});
});
}
This is the 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(),
);
} )
),
Here is the LandingScreenRoutes class:
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;
}
}
The application first checks if the snapshot has data:
And after it goes to the methods that fetch data from backend:
This should not happen. Because of this, the snapshot.hasdata ALWAYS will be false, and my test wont will pass.
How can I force the application wait until has data or other thing?
Upvotes: 0
Views: 548
Reputation: 381
Have you tried the expectLater?
Before:
expect(await findProductBox, findsWidgets);
After:
expectLater(await findProductBox, findsWidgets);
Edit: I replicated the problem here, try:
@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);
});
});
}
In the screen:
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(),
);
}
}))
]);
You can use pumpAndSettle to wait until all asynchronous methods are resolved and the parameter skipOffstage: false has the function to check items that are outside the screen boundary, very useful for testing with lists, as some items may not be visible
Upvotes: 0