How to make widget test wait until Bloc has updated the state?
Asked Answered
S

1

6

I have a EmailScreen (stateful widget) that has a text input, and a button. The button is only enabled when a valid email is input.

I'm using Bloc, and my screen has InitialEmailState and ValidEmailInputState, and it works fine when I run the app.

In my widget test, the second expectation is failing before bloc has a chance to update the state:


  testWidgets('when valid email is input, button is enabled', (tester) async {
    const validEmail = '[email protected]';

    emailBloc.listen((event) {
      print('NEW EVENT: ' + event.toString());
    });

    await bootUpWidget(tester, emailScreen);

    final BottomButton button = tester.widget(
        find.widgetWithText(BottomButton, 'CONTINUE'));

    expect(button.enabled, isFalse);

    await tester.enterText(find.byType(TextInputScreen), validEmail);
    await tester.pumpAndSettle();

    expect(button.enabled, isTrue);
  });

And here's the output I'm getting:

NEW EVENT: InitialEmailState
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure object was thrown running a test:
  Expected: true
  Actual: <false>

...

The test description was:
  when valid email is input, button is enabled
════════════════════════════════════════════════════════════════════════════════════════════════════
Test failed. See exception logs above.
The test description was: when valid email is input, button is enabled

NEW EVENT: InputValidEmailState
✖ when valid email is input, button is enabled
Exited (1)

As you can see, it prints the initial state, fails the second expectation, and then prints the expected state.

Thanks in advance :)

== UPDATE ==

We managed to get this to work by adding LiveTestWidgetsFlutterBinding(); to the start of our main. But it doesn't feel like a good solution.

Scopoline answered 23/1, 2020 at 19:33 Comment(0)
B
5

Encountered the same issue with BLoC driven UI widgets, I found the solution is pretty simple: use expectLater() rather than expect() to wait for BLoC state update:

testWidgets('test widgets driven by BLoC', (tester) async {
  await tester.pumpWidget(yourWidget);

  await tester.tap(loginButton); // Do something like tapping a button, entering text
  await tester.pumpAndSettle();

  await expectLater(findSomething, findsOneWidget); // IMPRTANT: use `expectLater` to wait for BLoC state update
  expect(findSomethingElse, findsOneWidget); // Subsequently you can use normal version `expect` until next `tester.pumpAndSettle()`
}

I put breakpoints into the BLoC to figure out what's the problem, it turns out that without using expectLater, the normal expect is being evaluated before the BLoC stream emits a new state. That is to say BLoC does emit a new state, but at that time the test case already runs to the end.

By using expectLater, it is being evaluated after the new state is emitted.

Barrier answered 16/11, 2020 at 23:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.