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.
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'.
Most helpful comment
Published v5.1.0 馃帀