diff --git a/packages/bloc_test/lib/src/bloc_test.dart b/packages/bloc_test/lib/src/bloc_test.dart index ca3275f9944..d1825035e85 100644 --- a/packages/bloc_test/lib/src/bloc_test.dart +++ b/packages/bloc_test/lib/src/bloc_test.dart @@ -148,6 +148,7 @@ void blocTest, State>( dynamic Function(B bloc)? verify, dynamic Function()? errors, FutureOr Function()? tearDown, + BlocObserver? observer, dynamic tags, }) { test.test( @@ -164,6 +165,7 @@ void blocTest, State>( verify: verify, errors: errors, tearDown: tearDown, + observer: observer, ); }, tags: tags, @@ -184,10 +186,12 @@ Future testBloc, State>({ dynamic Function(B bloc)? verify, dynamic Function()? errors, FutureOr Function()? tearDown, + BlocObserver? observer, }) async { var shallowEquality = false; final unhandledErrors = []; - final localBlocObserver = Bloc.observer; + final firstObserver = Bloc.observer; + final localBlocObserver = observer ?? Bloc.observer; final testObserver = _TestBlocObserver( localBlocObserver, unhandledErrors.add, @@ -206,7 +210,9 @@ Future testBloc, State>({ await act?.call(bloc); } catch (error) { if (errors == null) rethrow; - unhandledErrors.add(error); + if (!unhandledErrors.contains(error)) { + unhandledErrors.add(error); + } } if (wait != null) await Future.delayed(wait); await Future.delayed(Duration.zero); @@ -241,6 +247,8 @@ Alternatively, consider using Matchers in the expect of the blocTest rather than if (errors == null || !unhandledErrors.contains(error)) { rethrow; } + } finally { + Bloc.observer = firstObserver; } if (errors != null) test.expect(unhandledErrors, test.wrapMatcher(errors())); @@ -269,6 +277,37 @@ class _TestBlocObserver extends BlocObserver { _onError(error); super.onError(bloc, error, stackTrace); } + + @override + void onChange(BlocBase bloc, Change change) { + _localObserver.onChange(bloc, change); + super.onChange(bloc, change); + } + + @override + void onClose(BlocBase bloc) { + _localObserver.onClose(bloc); + super.onClose(bloc); + } + + @override + void onCreate(BlocBase bloc) { + _localObserver.onCreate(bloc); + super.onCreate(bloc); + } + + @override + void onEvent(Bloc bloc, Object? event) { + _localObserver.onEvent(bloc, event); + super.onEvent(bloc, event); + } + + @override + void onTransition( + Bloc bloc, Transition transition) { + _localObserver.onTransition(bloc, transition); + super.onTransition(bloc, transition); + } } String _diff({required dynamic expected, required dynamic actual}) { diff --git a/packages/bloc_test/test/bloc_bloc_test_test.dart b/packages/bloc_test/test/bloc_bloc_test_test.dart index 4a0cafac53d..80d36f5a1e4 100644 --- a/packages/bloc_test/test/bloc_bloc_test_test.dart +++ b/packages/bloc_test/test/bloc_bloc_test_test.dart @@ -5,6 +5,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; import 'blocs/blocs.dart'; +import 'blocs/failing_observer.dart'; class MockRepository extends Mock implements Repository {} @@ -662,6 +663,32 @@ Alternatively, consider using Matchers in the expect of the blocTest rather than }); }); }); + group('BlocObservers', () { + test( + 'FailingObserver', + () async { + Object? actualError; + final completer = Completer(); + await runZonedGuarded(() async { + unawaited( + testBloc( + build: () => CounterBloc(), + act: (bloc) => bloc.add(CounterEvent.increment), + errors: () => [isA()], + observer: FailingObserver(), + ).then((_) { + completer.complete(); + }), + ); + await completer.future; + }, (Object error, _) { + actualError = error; + if (!completer.isCompleted) completer.complete(); + }); + expect(actualError, isNull); + }, + ); + }); group('tearDown', () { late int tearDownCallCount; diff --git a/packages/bloc_test/test/blocs/failing_observer.dart b/packages/bloc_test/test/blocs/failing_observer.dart new file mode 100644 index 00000000000..d460d00c734 --- /dev/null +++ b/packages/bloc_test/test/blocs/failing_observer.dart @@ -0,0 +1,9 @@ +import 'package:bloc/bloc.dart'; + +class FailingObserver extends BlocObserver { + @override + void onEvent(Bloc bloc, Object? event) { + super.onEvent(bloc, event); + throw StateError('catch me'); + } +}