8

Information:
I have created a sample Flutter unit test to test the login screen where I have email & password as input field and a login button.

Requirement:
Need to test false cases and for that, I have written code as per the below steps.

  1. Open main.dart
  2. Filled the email & password field
  3. onTap event is done on the login button. Over here API will be called and loader is displayed on the screen until API gets a success or failure response.
  4. Need to check if failure dialog is displayed with a message.

Issue/Query:
Now when the API is calling I want to wait when the loader is visible until the loader is gone. So, as of now I just put a manual delay to execute the next code but I want to make it dynamic. So, let me know how we can put dynamic delay based on the loader visible?

Code:

void main() {
  group('App Test', () {
    IntegrationTestWidgetsFlutterBinding.ensureInitialized();

    testWidgets('Login Fail Test', (WidgetTester tester) async {
      await app.main();
      await tester.pumpAndSettle();

      await tester.pump(new Duration(seconds: 2));

      final emailField = find.byType(TextFormField).first;
      final passwordField = find.byType(TextFormField).last;
      final loginButton = find.byType(RaisedButton).first;

      await tester.enterText(emailField, 'Test');
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 1));

      await tester.enterText(passwordField, 'Test123');
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 1));

      await tester.tap(loginButton);
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 3));

     
      final dialog = find.byType(AlertDialog).first;
      await tester.element(dialog);
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 1));

      final dialogButton = find.byType(FlatButton).first;
      await tester.tap(dialogButton);
      await tester.pumpAndSettle();
      await tester.pump(new Duration(seconds: 2));
    });
}
Patel Pinkal
  • 8,984
  • 4
  • 28
  • 50
  • no official support yet, there are a couple issues on the flutter SDK https://github.com/flutter/flutter/issues/73355 apparently there is only one workaround function so far, but it's not well integrated in the test runner – Leonardo Nov 26 '21 at 11:23

2 Answers2

5

I have a file called utils.dart for functionality like this. In this case I use the following function which will basically poll until the finder is valid

// utils.dart

Future<void> pumpUntilFound(
  WidgetTester tester,
  Finder finder, {
  Duration timeout = const Duration(seconds: 10),
}) async {
  bool timerDone = false;
  final timer = Timer(timeout, () => timerDone = true);
  while (timerDone != true) {
    await tester.pump();

    final found = tester.any(finder);
    if (found) {
      timerDone = true;
    }
  }
  timer.cancel();
}

You can also make it throw an exception if it times out, but the error messages aren't helpful, so I usually follow it up with an expect

It would look like

// my_test.dart

final fab = find.byKey(const ValueKey('fab'));
await pumpUntilFound(widgetTester, fab);
expect(fab, findsOneWidget);
baconcheese113
  • 843
  • 2
  • 13
  • 27
  • This will still not make the dynamic waiting because we are passing fix timeout which will exit once we wait until that time. – Patel Pinkal Oct 18 '22 at 07:27
  • @PatelPinkal It's dynamic because it waits until the Finder is found, you can increase the timeout to whatever length of time you find acceptable to wait. If your api request takes more than 10 seconds to resolve there's likely an error, but you can adjust the timeout. – baconcheese113 Oct 23 '22 at 22:58
  • But ultimately it will call a pump every time when the timer is executed until the finder is found which I think it's not good practice. – Patel Pinkal Oct 27 '22 at 07:04
  • 2
    Your question is about waiting until the `Finder is visible`, there isn't a way for a finder to become visible without rendering a new frame (which is what pump() does). I agree that monitoring your specific network request to wait for it to complete would be more effective, but the answer above is the solution to your question – baconcheese113 Oct 30 '22 at 02:35
-2

Try wrapping like this:

testWidgets('test',
    (WidgetTester tester) async {
    await tester.runAsync(() async {

      // test code here

    });
 });

If you use:

await tester.pumpAndSettle();

And then:

  final widget = find.byKey(Key('whatever'));

It will find dinamically

Maikzen
  • 1,504
  • 2
  • 6
  • 18
  • I don't want to find any widget or finder. I need to wait until the widget is visible instead of static delay by duration. – Patel Pinkal Oct 28 '21 at 13:02