Bloc: Can we listen to multiple state in the same BlocListener?

Created on 5 Nov 2019  路  1Comment  路  Source: felangel/bloc

I am having this code where I am listening to two states in the BlocListener:
https://github.com/mihai9112/reazzon/blob/feature/testing/lib/src/pages/login_page.dart#L55

According to a question I raised on StackOverflow, one test is failing (the second state listened) with the assertion failing to find the widget in question.

https://stackoverflow.com/questions/58709789/test-failing-on-bloclistener-in-flutter-when-testing-widget

Is this a limitation of the BlocListener? Or am I missing something?

question

Most helpful comment

Hi @mihai9112 馃憢
Thanks for opening an issue!

The problem you're running into is a timing issue. When you use whenListen it immediately seeds the bloc with that stream of states so when BlocListener subscribes it will begin reacting right away.

If you simply want to test the SnackBar functionality I would recommend refactoring your tests like:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:reazzon/src/login/login_bloc.dart';
import 'package:reazzon/src/login/login_state.dart';
import 'package:reazzon/src/pages/login_page.dart';
import 'package:bloc_test/bloc_test.dart';

import '../authentication_tests/authentication_mock.dart';

void main() async {
  AuthenticationRepositoryMock _authenticationRepositoryMock;
  LoginBlocMock _loginBloc;
  final snackBarFailureFinder = find.byKey(Key("snack_bar_failure"));
  final snackBarLoadingFinder = find.byKey(Key("snack_bar_loading"));

  Widget makeTestableWidget() {
    return BlocProvider<LoginBloc>(
      builder: (context) => _loginBloc,
      child: MaterialApp(
          home: Scaffold(
        body: LoginPage(),
      )),
    );
  }

  setUp(() {
    _authenticationRepositoryMock = AuthenticationRepositoryMock();
    _loginBloc =
        LoginBlocMock(authenticationRepository: _authenticationRepositoryMock);
  });

  testWidgets('Show snack bar when state is LoginFailure',
      (WidgetTester tester) async {
    //Arrange
    final expectedStates = [
      LoginInitial(),
      LoginFailure(
          error: "Could not find user. Please try different credentials")
    ];

    whenListen(_loginBloc, Stream.fromIterable(expectedStates));

    //Act
    await tester.pumpWidget(makeTestableWidget());
    expect(snackBarFailureFinder, findsNothing);
    await tester.pump();

    //Assert
    expect(snackBarFailureFinder, findsOneWidget);
  });

  testWidgets('Show snack bar when state is LoginLoading',
      (WidgetTester tester) async {
    //Arrange
    final expectedStates = [LoginInitial(), LoginLoading()];

    whenListen(_loginBloc, Stream.fromIterable(expectedStates));

    //Act
    await tester.pumpWidget(makeTestableWidget());
    expect(snackBarLoadingFinder, findsNothing);
    await tester.pump();

    //Assert
    expect(snackBarLoadingFinder, findsOneWidget);
  });
}

Hope that helps 馃憤

>All comments

Hi @mihai9112 馃憢
Thanks for opening an issue!

The problem you're running into is a timing issue. When you use whenListen it immediately seeds the bloc with that stream of states so when BlocListener subscribes it will begin reacting right away.

If you simply want to test the SnackBar functionality I would recommend refactoring your tests like:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:reazzon/src/login/login_bloc.dart';
import 'package:reazzon/src/login/login_state.dart';
import 'package:reazzon/src/pages/login_page.dart';
import 'package:bloc_test/bloc_test.dart';

import '../authentication_tests/authentication_mock.dart';

void main() async {
  AuthenticationRepositoryMock _authenticationRepositoryMock;
  LoginBlocMock _loginBloc;
  final snackBarFailureFinder = find.byKey(Key("snack_bar_failure"));
  final snackBarLoadingFinder = find.byKey(Key("snack_bar_loading"));

  Widget makeTestableWidget() {
    return BlocProvider<LoginBloc>(
      builder: (context) => _loginBloc,
      child: MaterialApp(
          home: Scaffold(
        body: LoginPage(),
      )),
    );
  }

  setUp(() {
    _authenticationRepositoryMock = AuthenticationRepositoryMock();
    _loginBloc =
        LoginBlocMock(authenticationRepository: _authenticationRepositoryMock);
  });

  testWidgets('Show snack bar when state is LoginFailure',
      (WidgetTester tester) async {
    //Arrange
    final expectedStates = [
      LoginInitial(),
      LoginFailure(
          error: "Could not find user. Please try different credentials")
    ];

    whenListen(_loginBloc, Stream.fromIterable(expectedStates));

    //Act
    await tester.pumpWidget(makeTestableWidget());
    expect(snackBarFailureFinder, findsNothing);
    await tester.pump();

    //Assert
    expect(snackBarFailureFinder, findsOneWidget);
  });

  testWidgets('Show snack bar when state is LoginLoading',
      (WidgetTester tester) async {
    //Arrange
    final expectedStates = [LoginInitial(), LoginLoading()];

    whenListen(_loginBloc, Stream.fromIterable(expectedStates));

    //Act
    await tester.pumpWidget(makeTestableWidget());
    expect(snackBarLoadingFinder, findsNothing);
    await tester.pump();

    //Assert
    expect(snackBarLoadingFinder, findsOneWidget);
  });
}

Hope that helps 馃憤

Was this page helpful?
0 / 5 - 0 ratings