Bloc: Tests are crashing after switching to 1.0.0 from 0.19.0

Created on 29 Oct 2019  路  5Comments  路  Source: felangel/bloc

Describe the bug
The tests are failed with the following error when I'm testing widget with MultiBlocListener on v1.0.0.

Exception has occurred.
TestFailure (Expected: exactly one matching node in the widget tree
  Actual: ?:<zero widgets with key [<'EMAIL_LOGIN_EMAIL_TEXT_FIELD'>] (ignoring offstage widgets)>
   Which: means none were found but one was expected
)

To Reproduce
Steps to reproduce the behavior:

  1. Mock block and run tests
  2. Widget is build but during run it doesn't go to builder: (BuildContext context, LoginState loginState)
  3. As result widget is not build correctly
  4. The error occurs.

Expected behavior
In version 0.19.0 it goes to the builder: (BuildContext context, LoginState loginState) and build the widget.

Test

setUp(() {
    authBloc = MockAuthenticationBloc();
    loginBloc = MockLoginBloc();

    testWidget = wrapWidget(Scaffold(
        body: BlocProvider.value(
          value: loginBloc,
          child: EmailLoginForm(authBloc: authBloc, loginBloc: loginBloc),
        )
    ), null);
  });

testWidgets('form is initially displayed', (WidgetTester tester) async {
    when(loginBloc.state).thenAnswer((_) => LoginState.loading());

    await tester.pumpWidget(testWidget);
    await tester.pumpAndSettle(); // to wait for localization

    expect(find.byKey(EMAIL_LOGIN_EMAIL_TEXT_FIELD), findsOneWidget);
    expect(find.byKey(EMAIL_LOGIN_PASSWORD_TEXT_FIELD), findsOneWidget);
    expect(find.byKey(EMAIL_LOGIN_SUBMIT_BUTTON), findsOneWidget);
  });

Widget

@override
  Widget build(BuildContext context) {
    return MultiBlocListener(
      listeners: [
        BlocListener<LoginBloc, LoginState>(
          bloc: _loginBloc,
          listener: (context, state) {
            if (_loginSucceeded(state)) {
              widget.tracker.trackEvent(event: Events.LOGIN_SUCCESS);
              _authBloc.onLogin(token: state.token);
            }
          },
        ),
        BlocListener<AuthenticationBloc, AuthenticationState>(
          bloc: _authBloc,
          listener: (context, state) {
            if (state.isAuthenticated) {
              Navigator.pushReplacementNamed(context, Constants.ROUTE_HOME);
            }
          },
        ),
      ],
      child: BlocBuilder<LoginBloc, LoginState>(
        bloc: _loginBloc,
        builder: (BuildContext context, LoginState loginState) {
          if (_loginFailed(loginState)) {
            buttonEnabled = true;
            widget.tracker.trackEvent(event: Events.LOGIN_FAILED);

            onWidgetDidBuild(() {
              final String errorMessage = _isNetworkError(loginState.error)
                  ? AppLocalizations.of(context).loginNetworkErrorMessage
                  : AppLocalizations.of(context).loginErrorMessage;
              _displayLoginErrorDialog(errorMessage);
            });
          }

          return _form(loginState);
        },
      ),
    );
  }

*Logs *
There is no errors in flutter analyze and flutter doctor

question

Most helpful comment

If you want to just mock the current state of the bloc you can do:

when(bloc.state).thenReturn(LoginState.loading());

Use whenListen if you want to mock the bloc stream. In your test you will likely need to do something like:

// Set the bloc's stream to empty
whenListen(loginBloc, Stream<LoginState>.empty());
// Set the bloc's state to Loading
when(loginBloc.state).thenReturn(LoginState.loading());

Hope that helps 馃憤

All 5 comments

Hi @Den-Ree 馃憢
Thanks for opening an issue!

The problem is in 1.0.0 you no longer access the state stream via bloc.state because the bloc itself extends Stream.

You need to change when(loginBloc.state).thenAnswer((_) => LoginState.loading());

to

whenListen(loginBloc, Stream<LoginState>.fromIterable([LoginState.loading()));

whenListen comes from the bloc_test package.

Hope that helps and sorry for any inconvenience!

@felangel thanks for the answer. But the problem is still happens. When I debug the test then code never go to inside. If I correctly understand that it should be called during building the widget or not?

builder: (BuildContext context, LoginState loginState) {
....
}

If you want to just mock the current state of the bloc you can do:

when(bloc.state).thenReturn(LoginState.loading());

Use whenListen if you want to mock the bloc stream. In your test you will likely need to do something like:

// Set the bloc's stream to empty
whenListen(loginBloc, Stream<LoginState>.empty());
// Set the bloc's state to Loading
when(loginBloc.state).thenReturn(LoginState.loading());

Hope that helps 馃憤

@felangel thanks a lot for the help, it works as you suggested. But I noticed the following thing, which is not clear for me.

During running the test, the first time state are null in
builder: (BuildContext context, LoginState loginState)

and only for the second time I get the needed state.

@Den-Ree can you please provide a link to a sample app which illustrates the issue you're seeing? If you're initially seeing a null state that means that the bloc's state has not been properly stubbed. Are you using when(loginBloc.state).thenReturn(LoginState.loading());?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MahdiPishguy picture MahdiPishguy  路  3Comments

timtraversy picture timtraversy  路  3Comments

rsnider19 picture rsnider19  路  3Comments

abinvp picture abinvp  路  3Comments

komapeb picture komapeb  路  3Comments