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!