I'm testing my Flutter mobile application. I'm using flutter_bloc
as state management solution and bloc_test
to test my BLoCs.
My application interacts with a remote Cloud Firestore database to store data. The BLoC responsible for managing all interactions with the database, uses a repository which exposes all the methods for carrying out the CRUD operations.
Here is the code for the TripsRepository
interface:
abstract class TripsRepository {
/// Deletes all the trips saved in the database.
Future<void> clear();
/// Deletes the given [trip] from the list of user's trips.
Future<void> delete({Trip trip});
/// Inserts the given [trip] into the list of user's trips.
Future<void> insert({Trip trip});
/// Returns a stream of containing a list of [Trip] objects sorted in descending order
/// according to starting time of the trip.
Stream<List<Trip>> trips();
/// Updates the given [trip] in the list of user's trips.
Future<void> update({Trip trip});
}
As you can see, the trips()
method returns a Stream
containing the list of all Trip
objects stored in the Firebase database. In the TripsBloc
I subscribe to this Stream
and I listen for changes.
Here is the code for the TripsBloc
:
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
import '../../../data/trip.dart';
import '../../repositories/trips/trips_repository.dart';
part 'trips_event.dart';
part 'trips_state.dart';
class TripsBloc extends Bloc<TripsEvent, TripsState> {
/// Trips repository used to perform CRUD operations.
final TripsRepository tripsRepository;
/// Subscription to trips database changes.
StreamSubscription _tripsSubscription; // <---------------- Stream subscription.
TripsBloc({@required this.tripsRepository})
: assert(tripsRepository != null),
super(TripsInitial());
@override
Stream<TripsState> mapEventToState(
TripsEvent event,
) async* {
yield TripsLoadInProgress();
if (event is LoadTrips) {
yield* _mapLoadTripsToState();
} else if (event is AddTrip) {
yield* _mapAddTripToState(event);
} else if (event is UpdateTrip) {
yield* _mapUpdateTripToState(event);
} else if (event is DeleteTrip) {
yield* _mapDeleteTripToState(event);
} else if (event is TripsCleared) {
yield* _mapTripsClearedToState();
} else if (event is TripsUpdated) {
yield* _mapTripsUpdatedToState(event);
}
}
// Cancels the _tripsSubscription when the TripsBloc is closed.
@override
Future<void> close() {
_tripsSubscription?.cancel();
return super.close();
}
// This method subscribes to the Stream and listen for changes.............
Stream<TripsState> _mapLoadTripsToState() async* {
_tripsSubscription?.cancel();
try {
_tripsSubscription = tripsRepository.trips().listen(
(List<Trip> trips) => add(TripsUpdated(trips: trips)),
);
} catch (_) {
yield TripsLoadFailure();
}
}
Stream<TripsState> _mapAddTripToState(AddTrip event) async* {
try {
tripsRepository.insert(trip: event.trip);
} catch (_) {
yield TripsLoadFailure();
}
}
Stream<TripsState> _mapUpdateTripToState(UpdateTrip event) async* {
try {
tripsRepository.update(trip: event.trip);
} catch (_) {
yield TripsLoadFailure();
}
}
Stream<TripsState> _mapDeleteTripToState(DeleteTrip event) async* {
try {
tripsRepository.delete(trip: event.trip);
} catch (_) {
yield TripsLoadFailure();
}
}
Stream<TripsState> _mapTripsClearedToState() async* {
final currentState = state;
if (currentState is TripsLoadSuccess) {
List<Trip> trips = currentState.trips;
try {
trips.forEach((trip) => tripsRepository.delete(trip: trip));
} catch (_) {
yield TripsLoadFailure();
}
}
}
// When we load our trips, we are subscribing to the TripsRepository
// and every time a new trip comes in, we add a TripsUpdated event.
// We then handle all TodosUpdates via the following method.
Stream<TripsState> _mapTripsUpdatedToState(TripsUpdated event) async* {
if (event.trips.isEmpty) {
yield TripsLoadSuccessEmpty();
} else if (event.trips.last.arrivalTime == null) {
yield TripsLoadSuccessActive(trips: event.trips);
} else {
yield TripsLoadSuccessNotActive(trips: event.trips);
}
}
}
To test TripsBloc
I use the bloc_test
library, and I use the mockito
library to mock the TripsRepository
. I think the error could be in the setUp
method when I mock the trips()
method of the repository, but I don't know what I'm getting wrong.
These are the tests that I want to perform:
import 'package:covtrack/business/blocs/trips/trips_bloc.dart';
import 'package:covtrack/business/repositories/trips/trips_repository.dart';
import 'package:covtrack/data/coordinates.dart';
import 'package:covtrack/data/place.dart';
import 'package:covtrack/data/trip.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:bloc_test/bloc_test.dart';
class MockTripsRepository extends Mock implements TripsRepository {}
void main() {
group('TripsBloc', () {
TripsRepository tripsRepository;
TripsBloc tripsBloc;
final trip1 = Trip(
tripId: 'ABCD1234',
reason: 'Proven work needs',
startingTime: DateTime.now(),
arrivalTime: null,
source: Place(
placeId: 'FGHU8976',
coords: Coordinates(latitude: 44.12345, longitude: 11.3456),
formattedAddress: 'via Rossi 1, Bologna (BO) Italy',
name: 'via Rossi 1, Bologna (BO) Italy',
),
destination: Place(
placeId: 'QWPO4566',
coords: Coordinates(latitude: 44.8880, longitude: 11.4312),
formattedAddress: 'via Verdi 3, Bologna (BO) Italy',
name: 'Best Supermarket',
),
stops: [],
);
setUp(() {
tripsRepository = MockTripsRepository();
when(tripsRepository.trips()).thenAnswer((_) => Stream.value(<Trip>[])); //<--------------------- I THINK THE ERROR IS HERE!!!!!!!!!!!!!!!!!
tripsBloc = TripsBloc(tripsRepository: tripsRepository);
});
blocTest(
'should emit TripsLoadSuccessEmpty when trips loaded for the first time',
build: () async => tripsBloc,
act: (bloc) async => bloc.add(LoadTrips()),
expect: [
TripsLoadInProgress(),
TripsLoadSuccessEmpty(),
],
);
blocTest(
'should add a trip to the list in response to an AddTrip event',
build: () async => tripsBloc,
act: (bloc) async => bloc..add(LoadTrips())..add(AddTrip(trip: trip1)),
expect: [
TripsLoadInProgress(),
TripsLoadSuccessEmpty(),
TripsLoadSuccessActive(trips: [trip1])
],
);
});
}
When I run the tests of the BLoC for the LoadTrips
event or the sequence of events [LoadTrips
, AddTrip
] my tests fail with the following error, as if no status had been issued following the event.
Expected: [
TripsLoadInProgress:TripsLoadInProgress,
TripsLoadSuccessEmpty:TripsLoadSuccessEmpty
]
Actual: [TripsLoadInProgress:TripsLoadInProgress]
Which: shorter than expected at location [1]
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>
✖ TripsBloc should emit TripsLoadSuccessEmpty when trips loaded for the first time
Expected: [
TripsLoadInProgress:TripsLoadInProgress,
TripsLoadSuccessEmpty:TripsLoadSuccessEmpty,
TripsLoadSuccessActive:TripsLoadSuccessActive { trips: [ Trip {
tripId: ABCD1234,
reason: Proven work needs,
startingTime: 2020-07-06 14:03:05.726559,
arrivalTime: null,
source: Place {
placeId: FGHU8976,
coords: Coordinates { latitude: 44.12345, longitude: 11.3456 },
formattedAddress: via Rossi 1, Bologna (BO) Italy,
name: via Rossi 1, Bologna (BO) Italy,
},
destination: Place {
placeId: QWPO4566,
coords: Coordinates { latitude: 44.888, longitude: 11.4312 },
formattedAddress: via Verdi 3, Bologna (BO) Italy,
name: Best Supermarket,
},
stops: [],
}] }
]
Actual: [TripsLoadInProgress:TripsLoadInProgress]
Which: shorter than expected at location [1]
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>
Aucun commentaire:
Enregistrer un commentaire