6

In my State class I have declared a future:

Future<void> _testFuture;

and assign it in the initState like this:

super.initState();

_testFuture = Future(() async {
      await Future.value(1); //can be any computation
    });

and use it in the FutureBuilder like this:

 FutureBuilder(
              future: _testFuture,
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.done)
                  return Text('Hi');
                else
                  return Center(
                    child: CircularProgressIndicator(),
                  );
              },
            ),

This works fine when running the app normally with flutter run but when I try widget testing this using flutter test test/widget_test.dart:

void main() {
  testWidgets('Testing', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.runAsync(() async {
      await tester.pumpWidget(MyApp());
      await tester.pumpAndSettle();
    });
}

it fails with :

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following assertion was thrown while running async test code:
pumpAndSettle timed out

However, if I assign the future this way the same test passes with no issues:

super.initState();

Future<void> testFutures() async {
      await Future.value(1);
    }

_testFuture = testFutures();

What is the difference between the two ways of assigning the future ?

Calvin Gonsalves
  • 1,738
  • 11
  • 15

2 Answers2

3

The default timeout for pumpAndSettle is ten minutes, as mentioned in the docs. Checking your code, the test should run fine. Initializing Future<void> either way shouldn't cause any issue when I tried it locally.

Here's a sample that you can also try. Text should be shown once Future.delayed() finishes. One of the Future<void> is initialized on initState().

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> 

  late Future<void> _testFuture1;

  Future<void> _testFuture2() async {
    // await Future.value(1);
    await Future.delayed(Duration(seconds: 5), () {
      debugPrint('Test 2: \"Hello from the future!\"');
    });
  }

  @override
  void initState() {
    super.initState();
    _testFuture1 = Future(() async {
      // await Future.value(1);
      await Future.delayed(Duration(seconds: 5), () {
        debugPrint('Test 1: \"Hello from the future!\"');
      });
    });
  }

  FutureBuilder _futureBuilder1() {
    return FutureBuilder(
        future: _testFuture1,
        builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
          if (snapshot.connectionState == ConnectionState.done)
            return Text('Test 1 Done!');
          else
            return Center(
              child: CircularProgressIndicator(),
            );
          // return Text('Test 1 Done!');
        });
  }

  FutureBuilder _futureBuilder2() {
    return FutureBuilder(
        future: _testFuture2(),
        builder: (BuildContext context, AsyncSnapshot<dynamic> builder) {
          return Text('Test 2 Done!');
        });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            _futureBuilder1(),
            _futureBuilder2(),
          ],
        ),
      ),
    );
  }
}

Here's the test file

void main() {
  testWidgets('Future test', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.runAsync(() async{
      await tester.pumpWidget(MyApp());
      await tester.pump(); // both pump() and pumpAndSettle() works fine
    }).then((value) {
      // expect(find.text('Test 1 Done!'), findsOneWidget);
      // expect(find.text('Test 2 Done!'), findsOneWidget);
    });
  });
}

Demo

Test

Test

Here's my Flutter version for reference

[✓] Flutter (Channel stable, 2.2.1, on macOS 11.4 20F71 darwin-x64)
    • Flutter version 2.2.1
    • Framework revision 02c026b03c (6 weeks ago), 2021-05-27 12:24:44 -0700
    • Engine revision 0fdb562ac8
    • Dart version 2.13.1
Omatt
  • 8,564
  • 2
  • 42
  • 144
0

I've had the same issue, the problem is that you're using pumpAndSettle and an infinite animation (Circular progress indicator) - read more https://stackoverflow.com/a/67186625/7138472

Daniel
  • 452
  • 6
  • 14