LCOV - code coverage report
Current view: top level - src - bloc_test.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 35 36 97.2 %
Date: 2021-10-24 17:54:27 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : 
       3             : import 'package:bloc/bloc.dart';
       4             : import 'package:meta/meta.dart';
       5             : import 'package:test/test.dart' as test;
       6             : 
       7             : /// Creates a new `bloc`-specific test case with the given [description].
       8             : /// [blocTest] will handle asserting that the `bloc` emits the [expect]ed
       9             : /// states (in order) after [act] is executed.
      10             : /// [blocTest] also handles ensuring that no additional states are emitted
      11             : /// by closing the `bloc` stream before evaluating the [expect]ation.
      12             : ///
      13             : /// [setUp] is optional and should be used to set up
      14             : /// any dependencies prior to initializing the `bloc` under test.
      15             : /// [setUp] should be used to set up state necessary for a particular test case.
      16             : /// For common set up code, prefer to use `setUp` from `package:test/test.dart`.
      17             : ///
      18             : /// [build] should construct and return the `bloc` under test.
      19             : ///
      20             : /// [seed] is an optional `Function` that returns a state
      21             : /// which will be used to seed the `bloc` before [act] is called.
      22             : ///
      23             : /// [act] is an optional callback which will be invoked with the `bloc` under
      24             : /// test and should be used to interact with the `bloc`.
      25             : ///
      26             : /// [skip] is an optional `int` which can be used to skip any number of states.
      27             : /// [skip] defaults to 0.
      28             : ///
      29             : /// [wait] is an optional `Duration` which can be used to wait for
      30             : /// async operations within the `bloc` under test such as `debounceTime`.
      31             : ///
      32             : /// [expect] is an optional `Function` that returns a `Matcher` which the `bloc`
      33             : /// under test is expected to emit after [act] is executed.
      34             : ///
      35             : /// [verify] is an optional callback which is invoked after [expect]
      36             : /// and can be used for additional verification/assertions.
      37             : /// [verify] is called with the `bloc` returned by [build].
      38             : ///
      39             : /// [errors] is an optional `Function` that returns a `Matcher` which the `bloc`
      40             : /// under test is expected to throw after [act] is executed.
      41             : ///
      42             : /// [tearDown] is optional and can be used to
      43             : /// execute any code after the test has run.
      44             : /// [tearDown] should be used to clean up after a particular test case.
      45             : /// For common tear down code, prefer to use `tearDown` from `package:test/test.dart`.
      46             : ///
      47             : /// [tags] is optional and if it is passed, it declares user-defined tags
      48             : /// that are applied to the test. These tags can be used to select or
      49             : /// skip the test on the command line, or to do bulk test configuration.
      50             : ///
      51             : /// ```dart
      52             : /// blocTest(
      53             : ///   'CounterBloc emits [1] when increment is added',
      54             : ///   build: () => CounterBloc(),
      55             : ///   act: (bloc) => bloc.add(CounterEvent.increment),
      56             : ///   expect: () => [1],
      57             : /// );
      58             : /// ```
      59             : ///
      60             : /// [blocTest] can optionally be used with a seeded state.
      61             : ///
      62             : /// ```dart
      63             : /// blocTest(
      64             : ///   'CounterBloc emits [10] when seeded with 9',
      65             : ///   build: () => CounterBloc(),
      66             : ///   seed: () => 9,
      67             : ///   act: (bloc) => bloc.add(CounterEvent.increment),
      68             : ///   expect: () => [10],
      69             : /// );
      70             : /// ```
      71             : ///
      72             : /// [blocTest] can also be used to [skip] any number of emitted states
      73             : /// before asserting against the expected states.
      74             : /// [skip] defaults to 0.
      75             : ///
      76             : /// ```dart
      77             : /// blocTest(
      78             : ///   'CounterBloc emits [2] when increment is added twice',
      79             : ///   build: () => CounterBloc(),
      80             : ///   act: (bloc) {
      81             : ///     bloc
      82             : ///       ..add(CounterEvent.increment)
      83             : ///       ..add(CounterEvent.increment);
      84             : ///   },
      85             : ///   skip: 1,
      86             : ///   expect: () => [2],
      87             : /// );
      88             : /// ```
      89             : ///
      90             : /// [blocTest] can also be used to wait for async operations
      91             : /// by optionally providing a `Duration` to [wait].
      92             : ///
      93             : /// ```dart
      94             : /// blocTest(
      95             : ///   'CounterBloc emits [1] when increment is added',
      96             : ///   build: () => CounterBloc(),
      97             : ///   act: (bloc) => bloc.add(CounterEvent.increment),
      98             : ///   wait: const Duration(milliseconds: 300),
      99             : ///   expect: () => [1],
     100             : /// );
     101             : /// ```
     102             : ///
     103             : /// [blocTest] can also be used to [verify] internal bloc functionality.
     104             : ///
     105             : /// ```dart
     106             : /// blocTest(
     107             : ///   'CounterBloc emits [1] when increment is added',
     108             : ///   build: () => CounterBloc(),
     109             : ///   act: (bloc) => bloc.add(CounterEvent.increment),
     110             : ///   expect: () => [1],
     111             : ///   verify: (_) {
     112             : ///     verify(() => repository.someMethod(any())).called(1);
     113             : ///   }
     114             : /// );
     115             : /// ```
     116             : ///
     117             : /// **Note:** when using [blocTest] with state classes which don't override
     118             : /// `==` and `hashCode` you can provide an `Iterable` of matchers instead of
     119             : /// explicit state instances.
     120             : ///
     121             : /// ```dart
     122             : /// blocTest(
     123             : ///  'emits [StateB] when EventB is added',
     124             : ///  build: () => MyBloc(),
     125             : ///  act: (bloc) => bloc.add(EventB()),
     126             : ///  expect: () => [isA<StateB>()],
     127             : /// );
     128             : /// ```
     129             : ///
     130             : /// If [tags] is passed, it declares user-defined tags that are applied to the
     131             : /// test. These tags can be used to select or skip the test on the command line,
     132             : /// or to do bulk test configuration. All tags should be declared in the
     133             : /// [package configuration file][configuring tags]. The parameter can be an
     134             : /// [Iterable] of tag names, or a [String] representing a single tag.
     135             : ///
     136             : /// [configuring tags]: https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#configuring-tags
     137           1 : @isTest
     138             : void blocTest<B extends BlocBase<State>, State>(
     139             :   String description, {
     140             :   FutureOr<void> Function()? setUp,
     141             :   required B Function() build,
     142             :   State Function()? seed,
     143             :   Function(B bloc)? act,
     144             :   Duration? wait,
     145             :   int skip = 0,
     146             :   dynamic Function()? expect,
     147             :   Function(B bloc)? verify,
     148             :   dynamic Function()? errors,
     149             :   FutureOr<void> Function()? tearDown,
     150             :   dynamic tags,
     151             : }) {
     152           1 :   test.test(
     153             :     description,
     154           1 :     () async {
     155           2 :       await testBloc<B, State>(
     156             :         setUp: setUp,
     157             :         build: build,
     158             :         seed: seed,
     159             :         act: act,
     160             :         wait: wait,
     161             :         skip: skip,
     162             :         expect: expect,
     163             :         verify: verify,
     164             :         errors: errors,
     165             :         tearDown: tearDown,
     166             :       );
     167             :     },
     168             :     tags: tags,
     169             :   );
     170             : }
     171             : 
     172             : /// Internal [blocTest] runner which is only visible for testing.
     173             : /// This should never be used directly -- please use [blocTest] instead.
     174             : @visibleForTesting
     175           1 : Future<void> testBloc<B extends BlocBase<State>, State>({
     176             :   FutureOr<void> Function()? setUp,
     177             :   required B Function() build,
     178             :   State Function()? seed,
     179             :   Function(B bloc)? act,
     180             :   Duration? wait,
     181             :   int skip = 0,
     182             :   dynamic Function()? expect,
     183             :   Function(B bloc)? verify,
     184             :   dynamic Function()? errors,
     185             :   FutureOr<void> Function()? tearDown,
     186             : }) async {
     187           1 :   final unhandledErrors = <Object>[];
     188             :   var shallowEquality = false;
     189           1 :   final localObserver = Bloc.observer;
     190           2 :   final testObserver = _TestBlocObserver(localObserver, unhandledErrors.add);
     191             :   Bloc.observer = testObserver;
     192           2 :   await runZonedGuarded(
     193           1 :     () async {
     194           1 :       await setUp?.call();
     195           1 :       final states = <State>[];
     196             :       final bloc = build();
     197             :       // ignore: invalid_use_of_protected_member
     198           1 :       if (seed != null) bloc.emit(seed());
     199           4 :       final subscription = bloc.stream.skip(skip).listen(states.add);
     200             :       try {
     201           1 :         await act?.call(bloc);
     202             :       } catch (error) {
     203           1 :         unhandledErrors.add(error);
     204             :       }
     205           2 :       if (wait != null) await Future<void>.delayed(wait);
     206           2 :       await Future<void>.delayed(Duration.zero);
     207           2 :       await bloc.close();
     208             :       if (expect != null) {
     209             :         final dynamic expected = expect();
     210           2 :         shallowEquality = '$states' == '$expected';
     211           2 :         test.expect(states, test.wrapMatcher(expected));
     212             :       }
     213           2 :       await subscription.cancel();
     214           1 :       await verify?.call(bloc);
     215           1 :       await tearDown?.call();
     216             :     },
     217           1 :     (Object error, _) {
     218           1 :       if (shallowEquality && error is test.TestFailure) {
     219             :         // ignore: only_throw_errors
     220           1 :         throw test.TestFailure(
     221           1 :           '''${error.message}
     222             : WARNING: Please ensure state instances extend Equatable, override == and hashCode, or implement Comparable.
     223           1 : Alternatively, consider using Matchers in the expect of the blocTest rather than concrete state instances.\n''',
     224             :         );
     225             :       }
     226             :       // ignore: only_throw_errors
     227             :       if (errors == null) throw error;
     228           0 :       unhandledErrors.add(error);
     229             :     },
     230             :   );
     231           2 :   if (errors != null) test.expect(unhandledErrors, test.wrapMatcher(errors()));
     232             :   Bloc.observer = localObserver;
     233             : }
     234             : 
     235             : class _TestBlocObserver extends BlocObserver {
     236           1 :   _TestBlocObserver(this.localObserver, this._onError);
     237             : 
     238             :   final BlocObserver localObserver;
     239             :   final void Function(Object error) _onError;
     240             : 
     241           1 :   @override
     242             :   void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
     243           2 :     localObserver.onError(bloc, error, stackTrace);
     244           1 :     _onError(error);
     245           1 :     super.onError(bloc, error, stackTrace);
     246             :   }
     247             : }

Generated by: LCOV version 1.15