Bloc: Allow option to disable throwing BlocUnhandledErrorException in debug mode

Created on 5 May 2020  路  6Comments  路  Source: felangel/bloc

In our current bloc implementation, any unknown exception caught is thrown by bloc to be propagated to onError method in BlocDelegate and reported to our error reporting service.

After the update to the latest version, our bloc tests written using bloc_test to verify the unknown exceptions are failing. Because now BlocUnhandledErrorException is thrown in debug mode for unhandled exceptions thrown by bloc.

Is there a way to disable this behavior? If not, then it would be really helpful to be able to disable this option in debug mode.

enhancement bloc_test

Most helpful comment

Published v5.1.0 馃帀

All 6 comments

Hi @MohsinN 馃憢
Thanks for opening an issue!

I have a PR to update bloc_test to ignore errors unless you provide an errors Iterable.

Suppose the bloc throws an exception when null is added, the following test would still pass.

blocTest(
  'CounterBloc throws Exception when null is added',
  build: () async => CounterBloc(),
  act: (bloc) => bloc.add(null),
);

Furthermore, we can write expectations for the unhandled exceptions like:

blocTest(
  'CounterBloc throws Exception when null is added',
  build: () async => CounterBloc(),
  act: (bloc) => bloc.add(null),
  errors: [
    isA<Exception>(),
  ]
);

This change will be published in v5.1.0 of bloc_test.

@felangel Thanks a lot! This is exactly what I was hoping for. 馃挴
Any timeline on when this will be published?

@MohsinN should be published sometime tomorrow 馃憤

@felangel You rock! 馃

Published v5.1.0 馃帀

Hi @felangel 馃憢

Really appreciate all your effort but I am still facing the error. Replicated the error using code snippet from flutter_login example.

class LoginBloc extends Bloc<LoginEvent, LoginState> {
  final UserRepository userRepository;
  final AuthenticationBloc authenticationBloc;

  LoginBloc({
    @required this.userRepository,
    @required this.authenticationBloc,
  })  : assert(userRepository != null),
        assert(authenticationBloc != null);

  @override
  LoginState get initialState => LoginInitial();

  @override
  Stream<LoginState> mapEventToState(LoginEvent event) async* {
    if (event is LoginButtonPressed) {
      yield LoginLoading();
      try {
        final token = await userRepository.authenticate(
          username: event.username,
          password: event.password,
        );
        authenticationBloc.add(LoggedIn(token: token));
        yield LoginInitial();
      } on SocketException {
        rethrow;
      } catch (error) {
        yield LoginFailure(error: error.toString());
      }
    }
  }
}

Corresponding failing bloc test:

    blocTest('emits [LoginLoading, LoginFailure] on failure',
        build: () async {
          when(userRepository.authenticate(
            username: 'valid.username',
            password: 'valid.password',
          )).thenThrow(SocketException('test exception'));
          return loginBloc;
        },
        act: (bloc) => bloc.add(
              LoginButtonPressed(
                username: 'valid.username',
                password: 'valid.password',
              ),
            ),
        expect: [
          LoginLoading(),
        ],
        verify: (_) async {
          verifyNever(authenticationBloc.add(any));
        },
        errors: [isA<SocketException>()]);
  });

Test passing with alternate implementation

  test('emits [LoginLoading, LoginFailure] on failure 2', () async {
    runZoned(() {
      when(userRepository.authenticate(
        username: 'valid.username',
        password: 'valid.password',
      )).thenThrow(SocketException('test exception'));
      LoginBloc loginBloc = LoginBloc(
        userRepository: userRepository,
        authenticationBloc: authenticationBloc,
      );

      final expectedResponse = [
        LoginInitial(),
        LoginLoading(),
      ];
      expectLater(
        loginBloc,
        emitsInOrder(expectedResponse),
      );
      loginBloc.add(LoginButtonPressed(
        username: 'valid.username',
        password: 'valid.password',
      ));
    }, onError: (error, stackTrace) {
      if (error is BlocUnhandledErrorException) {
        expect(error.error, isA<SocketException>());
      }
    });
  });

Please correct me if I am wrong. Here SocketException should have been caught and verified in the errors but get the same error in the logs. This is in continuation of the above logic that SocketException would be later propagated to BlocDelegate to be reported.


Error log
C:srcflutterbinflutter.bat --no-color test --machine --plain-name "emits [LoginLoading, LoginFailure] on failure" testunitloginlogin_bloc_test.dart
package:bloc/src/bloc.dart 146:7 Bloc.onError.
package:bloc/src/bloc.dart 147:6 Bloc.onError
===== asynchronous gap ===========================
dart:async _StreamImpl.listen
package:bloc/src/bloc.dart 260:7 Bloc._bindEventsToStates
package:bloc/src/bloc.dart 59:5 new Bloc
package:flutter_login/login/bloc/login_bloc.dart new LoginBloc
testunitloginlogin_bloc_test.dart 22:17 main.

Unhandled error SocketException: test exception occurred in bloc Instance of 'LoginBloc'.

0 PostExpectation.thenThrow. (package:mockito/src/mock.dart:346:7)

1 Mock.noSuchMethod (package:mockito/src/mock.dart:123:45)

2 LoginBloc.mapEventToState (package:flutter_login/login/bloc/login_bloc.dart:31:44)

3 Bloc._bindEventsToStates. (package:bloc/src/bloc.dart:252:20)

4 Stream.asyncExpand.onListen. (dart:async/stream.dart:579:30)

5 StackZoneSpecification._registerUnaryCallback.. (package:stack_trace/src/stack_zone_specification.dart:129:26)

6 StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)

7 StackZoneSpecification._registerUnaryCallback. (package:stack_trace/src/stack_zone_specification.dart:129:14)

8 _rootRunUnary (dart:async/zone.dart:1196:13)

9 _CustomZone.runUnary (dart:async/zone.dart:1085:19)

10 _CustomZone.runUnaryGuarded (dart:async/zone.dart:987:7)

11 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:339:11)

12 _DelayedData.perform (dart:async/stream_impl.dart:594:14)

13 _StreamImplEvents.handleNext (dart:async/stream_impl.dart:710:11)

14 _PendingEvents.schedule. (dart:async/stream_impl.dart:670:7)

15 StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)

16 StackZoneSpecification._registerCallback. (package:stack_trace/src/stack_zone_specification.dart:119:48)

17 _rootRun (dart:async/zone.dart:1180:38)

18 _CustomZone.run (dart:async/zone.dart:1077:19)

19 _CustomZone.runGuarded (dart:async/zone.dart:979:7)

20 _CustomZone.bindCallbackGuarded. (dart:async/zone.dart:1019:23)

21 StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)

22 StackZoneSpecification._registerCallback. (package:stack_trace/src/stack_zone_specification.dart:119:48)

23 _rootRun (dart:async/zone.dart:1184:13)

24 _CustomZone.run (dart:async/zone.dart:1077:19)

25 _CustomZone.runGuarded (dart:async/zone.dart:979:7)

26 _CustomZone.bindCallbackGuarded. (dart:async/zone.dart:1019:23)

27 _microtaskLoop (dart:async/schedule_microtask.dart:43:21)

28 _startMicrotaskLoop (dart:async/schedule_microtask.dart:52:5)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ricktotec picture ricktotec  路  3Comments

RobPFarley picture RobPFarley  路  3Comments

hivesey picture hivesey  路  3Comments

MahdiPishguy picture MahdiPishguy  路  3Comments

shawnchan2014 picture shawnchan2014  路  3Comments