Testing a BLoC with StreamSubscription in Flutter

Viewed 1360

I'm testing the blocs of my Flutter application. I'm using flutter_bloc, bloc_test and mockito libraries in my project.

One of my blocs uses a repository with a method that returns a Stream of objects. I don't understand how to mock the repository and the StreamSubscription inside the bloc.

Here is the code for my BLoC:

class StopsTrackerBloc extends Bloc<StopsTrackerEvent, StopsTrackerState> {
  final LocationRepository locationRepository;

  final List<Stop> stops;

  StreamSubscription _locationSubscription;

  StopsTrackerBloc({
    @required this.locationRepository,
    @required this.stops,
  })  : assert(locationRepository != null),
        assert(stops != null),
        super(Ready(stops));

  @override
  Stream<StopsTrackerState> mapEventToState(StopsTrackerEvent event) async* {
    if (event is StartTracking) {
      yield* _mapStartTrackingToState();
    } else if (event is NewStopRecorded) {
      yield* _mapNewStopRecordedToState(event.stop);
    } else if (event is StopTracking) {
      yield* _mapStopTrackingToState();
    }
  }

  // Cancels the _locationSubscription when the StopsTrackerBloc is closed.
  @override
  Future<void> close() {
    _locationSubscription?.cancel();
    return super.close();
  }

  Stream<StopsTrackerState> _mapStartTrackingToState() async* {
    _locationSubscription?.cancel();
    _locationSubscription =
        locationRepository.locationStream.listen((Stop stop) {
      add(NewStopRecorded(stop: stop));
    });
  }

  Stream<StopsTrackerState> _mapNewStopRecordedToState(Stop stop) async* {
    stops.add(stop);
    yield Running(stops);
  }

  Stream<StopsTrackerState> _mapStopTrackingToState() async* {
    _locationSubscription?.cancel();
    yield Paused(stops);
  }
}

Here I tried running these tests:

class MockLocationRepository extends Mock implements LocationRepository {}

void main() {
  LocationRepository locationRepository;
  StopsTrackerBloc stopsTrackerBloc;
  final List<Stop> initialStops = [
    Stop(
      coords: Coordinates(latitude: 1111, longitude: 1111),
      time: DateTime.now(),
    ),
    Stop(
      coords: Coordinates(latitude: 2222, longitude: 2222),
      time: DateTime.now(),
    ),
    Stop(
      coords: Coordinates(latitude: 3333, longitude: 3333),
      time: DateTime.now(),
    ),
    Stop(
      coords: Coordinates(latitude: 4444, longitude: 4444),
      time: DateTime.now(),
    ),
  ];

  setUp(() {
    locationRepository = MockLocationRepository();
    stopsTrackerBloc = StopsTrackerBloc(
      locationRepository: locationRepository,
      stops: initialStops,
    );
  });

  tearDown(() {
    stopsTrackerBloc?.close();
  });

  group('StartTracking', () {
    final Stop newStop = Stop(
      coords: Coordinates(latitude: 5555, longitude: 5555),
      time: DateTime.now(),
    );

    blocTest(
      'emits [Running] and adds a new stop to the initial list of stops',
      build: () async {
        when(locationRepository.locationStream)
            .thenAnswer((_) => Stream.fromIterable([newStop]));
        return stopsTrackerBloc;
      },
      act: (bloc) => bloc.add(StartTracking()),
      expect: [Running(initialStops..add(newStop))],
    );
  });
}

Result of the test:

StartTracking emits [Running] and adds a new stop to the initial list of stops:

ERROR: Expected: [
            Running:Running { stops: [    Stop {  
                location: Coordinates { latitude: 1111.0, longitude: 1111.0 },  
                time: 2020-07-08 15:06:48.934837,  
              },     Stop {  
                location: Coordinates { latitude: 2222.0, longitude: 2222.0 },  
                time: 2020-07-08 15:06:48.934837,  
              },     Stop {  
                location: Coordinates { latitude: 3333.0, longitude: 3333.0 },  
                time: 2020-07-08 15:06:48.934837,  
              },     Stop {  
                location: Coordinates { latitude: 4444.0, longitude: 4444.0 },  
                time: 2020-07-08 15:06:48.934837,  
              },     Stop {  
                location: Coordinates { latitude: 5555.0, longitude: 5555.0 },  
                time: 2020-07-08 15:06:48.942809,  
              }] }
          ]
  Actual: []
   Which: shorter than expected at location [0]

package:test_api                             expect
package:bloc_test/src/bloc_test.dart 143:29  blocTest.<fn>.<fn>
===== asynchronous gap ===========================
dart:async                                   _asyncThenWrapperHelper
package:bloc_test/src/bloc_test.dart         blocTest.<fn>.<fn>
dart:async                                   runZoned
package:bloc_test/src/bloc_test.dart 135:11  blocTest.<fn>

I don't understand why the Actual emitted states is an empty list!

0 Answers
Related