Mocking and Testing Firestore Operations in Flutter Unit Tests | Part 1 (Documents and Collections)

A highly motivated software engineer especially experienced as a mobile team lead working with diverse teams from all over the globe. Both as a team player and a mentor, he enjoys building secure, well-tested and visually appealing applications. Being ambitious and hardworking, he loves writing technical articles and spends his free time answering questions on StackOverflow as well as working on open-source projects.
Introduction
This article examines Firestore operations with respect to Documents and Collections and demonstrates how to mock and test them.
The article was created using Flutter Channel stable, 2.8.1.
Prerequisites
Add the following packages to your pubspec.yaml file and run flutter pub get.
dependencies:
...
cloud_firestore: ^3.1.4
dev_dependencies:
...
test:
fake_cloud_firestore: ^1.2.0
This adds cloud_firestore which is the Cloud Firestore package, test which is the package used for unit tests and fake_cloud_firestore which is the mock Firestore package.
Problem Statement
We have a class FirestoreService which performs read and write operations to Firestore and we want to write a set of unit tests for this class.
The class is shown below:
import 'package:cloud_firestore/cloud_firestore.dart';
class FirestoreService {
const FirestoreService({required this.firestore});
final FirebaseFirestore firestore;
/// Collection Operations
Future<DocumentReference<Map<String, dynamic>>> addToCollection(
{required Map<String, dynamic> data,
required String collectionPath}) async {
return firestore.collection(collectionPath).add(data);
}
Future<QuerySnapshot<Map<String, dynamic>>> getFromCollection(
{required String collectionPath}) {
return firestore.collection(collectionPath).get();
}
Stream<QuerySnapshot<Map<String, dynamic>>> getSnapshotStreamFromCollection(
{required String collectionPath}) {
return firestore.collection(collectionPath).snapshots();
}
/// Document Operations
Future<void> deleteDocumentFromCollection(
{required String collectionPath, required String documentPath}) async {
return firestore.collection(collectionPath).doc(documentPath).delete();
}
Future<DocumentSnapshot<Map<String, dynamic>>> getFromDocument(
{required String collectionPath, required String documentPath}) {
return firestore.collection(collectionPath).doc(documentPath).get();
}
Future<void> setDataOnDocument(
{required Map<String, dynamic> data,
required String collectionPath,
required String documentPath}) {
return firestore.collection(collectionPath).doc(documentPath).set(data);
}
Stream<DocumentSnapshot<Map<String, dynamic>>> getSnapshotStreamFromDocument(
{required String collectionPath, required String documentPath}) {
return firestore.collection(collectionPath).doc(documentPath).snapshots();
}
Future<void> updateDataOnDocument(
{required Map<String, dynamic> data,
required String collectionPath,
required String documentPath}) {
return firestore.collection(collectionPath).doc(documentPath).update(data);
}
}
Testing Approach
Construction Injection
Since the class FirestoreService injects the Firestore object through its constructor, we can pass the FakeFirebaseFirestore object from the fake_cloud_firestore package and use that to test the methods in the class.
Mocking Strategy
The FakeFirebaseFirestore object works like the Firestore object and the read and write operations that can be used to retrieve and add data to the Firestore object can also be used to retrieve and add data to the FakeFirebaseFirestore object.
This helps us prepopulate the mock object right before we call our methods and helps confirm the expected state of the Firestore database.
Database State Set Up
Initial State
The initial state of the
FakeFirebaseFirestoreobject can be set up by calling the regular write operations on theCollectionReferenceandDocumentReference.For instance, we want the initial state of the database to have an object
{answer: 42}written to a documentlifeTheUniverseAndEverythingin collectionanswers, we write the code below to set it up:final FakeFirebaseFirestore fakeFirebaseFirestore = FakeFirebaseFirestore(); const String collectionPath = 'answers'; const String documentPath = 'lifeTheUniverseAndEverything'; const Map<String, dynamic> data = {'answer': 42}; await fakeFirebaseFirestore .collection(collectionPath) .doc(documentPath) .set(data);Final State
The final state of the
FakeFirebaseFirestoreobject can be gotten by calling the regular read operations on theCollectionReferenceandDocumentReference.Now, to get the final state of the
FakeFirebaseFirestoreobject from above, we write the code below:final DocumentSnapshot<Map<String, dynamic>> documentSnapshot = await fakeFirebaseFirestore .collection(collectionPath) .doc(documentPath) .get(); final Map<String, dynamic> actualData = documentSnapshot.data()!; print(actualData); // {'answer': 42}Assertion
Once we have our data, our database's initial state set up and the final state retrieved, we can assert that the data in the database matches the expected data.
Going by what we have above, we can write the assertion using the test package's
expectmethod like below:expect(actualData, data); /// Passes ✅
Testing The FirestoreService Class
We will test the FirestoreService class by following the approach described in the Testing Approach section above.
This section will be divided into three:
- Set-up (where the objects needed for the tests are created)
- Collection Operations Testing (where operations involving Collections are tested)
- Document Operations Testing (where operations involving Documents are tested)
Set-up
Create a test file firestore_service_test.dart and add the following code to it:
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
import 'package:test/test.dart';
void main() {
group('FirestoreService', () {
FakeFirebaseFirestore? fakeFirebaseFirestore;
const Map<String, dynamic> data = {'data': '42'};
setUp(() {
fakeFirebaseFirestore = FakeFirebaseFirestore();
});
});
}
This creates the FakeFirebaseFirestore object and the data needed for every test we'll write for the FirestoreService class. All of the tests are put inside the "FirestoreService" group.
Collection Operations Testing
Map Equality
This section will be testing Collection operations and this means we will be asserting that the collection contains the data. Typically, that is done by using the contains matcher.
Returns a matcher that matches if the match argument contains the expected value.
This is how an assertion using the
containsmatcher would be written:final List<int> intList = [1, 2]; final int intData = 2; expect(intList, contains(intData)); /// Passes ✅This passes but when it comes to Lists of Maps, it's a different case.
The assertion below fails:
final List<Map<String, dynamic>> mapList = [ {'data': 42} ]; final mapData = {'data': 42}; expect(mapList, contains(mapData)); /// Fails ❌This is because the
containsmatcher for Lists is implemented this way:return item.contains(_expected);which in turn uses the
containsmethod on List which is implemented like below:bool contains(Object? element) { for (E e in this) { if (e == element) return true; } return false; }The code above checks that the
eobject is the same object as theelementobject. And themapDataobject won't be the same as the one contained in the List. That explains why the test fails.
mapEquals
In order to get around this, we can make use of the mapEquals function that does the following:
Compares two maps for element-by-element equality.
Returns true if the maps are both null, or if they are both non-null, they have the same lengths and they contain the same keys associated with the same values. Returns false otherwise.
We can check if any of the element in the list is equal to the
mapDataobject by using themapEqualsfunction like this:final List<Map<String, dynamic>> mapList = [ {'data': 42} ]; final mapData = {'data': 42}; expect(mapList.any((element) => mapEquals(element, mapData)), true); /// Passes ✅This test passes because the equality check using
mapEqualscompares the two Map objects by confirming that the keys and values are the same.Custom Matcher
While using the check above would work for our tests, it's cleaner and more readable to follow the Flutter convention of using a Matcher to assert our test.
We move the logic
mapList.any((element) => mapEquals(element, mapData))into the Matcher like this below:class MapListContains extends Matcher { final Map<dynamic, dynamic> _expected; const MapListContains(this._expected); @override Description describe(Description description) { return description.add('contains ').addDescriptionOf(_expected); } @override bool matches(dynamic item, Map matchState) { if (item is List<Map>) { return item.any((element) => mapEquals(element, _expected)); } return false; } }The
MapListContainsclass extends theMatcherabstract class and overrides thedescribemethod and thematchesmethod. The assertion logic is implemented in thematchesmethod in theMapListContainsclass.So we can update the test to this below:
final List<Map<String, dynamic>> mapList = [ {'data': 42} ]; final mapData = {'data': 42}; expect(mapList, MapListContains(mapData)); /// Passes ✅And this test passes.
Set-up
Now, we move to the actual test set-up.
We first create a group "Collection Operations" to hold tests for collection operations.
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart'; import 'package:test/test.dart'; void main() { group('FirestoreService', () { FakeFirebaseFirestore? fakeFirebaseFirestore; const Map<String, dynamic> data = {'data': '42'}; setUp(() { fakeFirebaseFirestore = FakeFirebaseFirestore(); }); group('Collection Operations', () { }); }); }We, can add each test inside of the group.
Test For Adding To A Collection.
Future<DocumentReference<Map<String, dynamic>>> addToCollection( {required Map<String, dynamic> data, required String collectionPath}) async { return firestore.collection(collectionPath).add(data); }The
addToCollectionmethod above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore, to the service. - Adding the data to the collection via the service's
addToCollectionmethod. - Retrieving the actual data list in the collection via
fakeFirebaseFirestore. - Asserting that the actual data list contains the added data using the
MapListContainsmatcher.
This is shown below:
test('addToCollection adds data to given collection', () async { final FirestoreService firestoreService = FirestoreService(firestore: fakeFirebaseFirestore!); const String collectionPath = 'collectionPath'; await firestoreService.addToCollection( data: data, collectionPath: collectionPath); final List<Map<String, dynamic>> actualDataList = (await fakeFirebaseFirestore!.collection('collectionPath').get()) .docs .map((e) => e.data()) .toList(); expect(actualDataList, const MapListContains(data)); /// Passes ✅ });- Injecting the mock Firestore object,
Test For Getting Data From A Collection.
Future<QuerySnapshot<Map<String, dynamic>>> getFromCollection() { return firestore.collection('collectionPath').get(); }The
getFromCollectionmethod above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore, to the service. - Adding the data to the collection via the
fakeFirebaseFirestoreobject. - Retrieving the actual data list in the collection via the service's
getFromCollectionmethod. - Asserting that the data list contains the added data using the
MapListContainsmatcher.
This is shown below:
test('getFromCollection gets data from a given collection', () async { final FirestoreService firestoreService = FirestoreService(firestore: fakeFirebaseFirestore!); const String collectionPath = 'collectionPath'; await fakeFirebaseFirestore!.collection(collectionPath).add(data); final List<Map<String, dynamic>> dataList = (await firestoreService .getFromCollection(collectionPath: collectionPath)) .docs .map((e) => e.data()) .toList(); expect(dataList, const MapListContains(data)); /// Passes ✅ });- Injecting the mock Firestore object,
Test For Getting A Snapshot Stream From A Collection.
Stream<QuerySnapshot<Map<String, dynamic>>> getSnapshotStreamFromCollection( {required String collectionPath}) { return firestore.collection(collectionPath).snapshots(); }The
getSnapshotStreamFromCollectionmethod above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore, to the service - Adding the data to the collection via the
fakeFirebaseFirestoreobject. - Retrieving the expected snapshot stream from the
fakeFirebaseFirestoreobject and the actual snapshot stream from the service'sgetSnapshotStreamFromCollectionmethod. - Getting the data from the two snapshot streams and putting them into individual lists (the actual data list and the expected data list).
- Asserting that the actual data list is equal to the expected data list.
This is shown below:
test( 'getSnapshotStreamFromCollection returns a stream of QuerySnaphot containing the data added', () async { final FirestoreService firestoreService = FirestoreService(firestore: fakeFirebaseFirestore!); const String collectionPath = 'collectionPath'; final CollectionReference<Map<String, dynamic>> collectionReference = fakeFirebaseFirestore!.collection(collectionPath); await collectionReference.add(data); final Stream<QuerySnapshot<Map<String, dynamic>>> expectedSnapshotStream = collectionReference.snapshots(); final actualSnapshotStream = firestoreService .getSnapshotStreamFromCollection(collectionPath: collectionPath); final QuerySnapshot<Map<String, dynamic>> expectedQuerySnapshot = await expectedSnapshotStream.first; final QuerySnapshot<Map<String, dynamic>> actualQuerySnapshot = await actualSnapshotStream.first; final List<Map<String, dynamic>> expectedDataList = expectedQuerySnapshot.docs.map((e) => e.data()).toList(); final List<Map<String, dynamic>> actualDataList = actualQuerySnapshot.docs.map((e) => e.data()).toList(); expect(actualDataList, expectedDataList); /// Passes ✅ });- Injecting the mock Firestore object,
Document Operations Testing
Test For Deleting A Document From A Collection.
Future<void> deleteDocumentFromCollection( {required String collectionPath, required String documentPath}) async { return firestore.collection(collectionPath).doc(documentPath).delete(); }The
deleteDocumentFromCollectionmethod above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore, to the service. - Adding the data to the collection via the
fakeFirebaseFirestoreobject. - Deleting the document at the collection via the service's
deleteDocumentFromCollectionmethod. - Getting the data at the document's path from the
fakeFirebaseFirestore. - Asserting that the document does not exist.
This is shown below:
test('deleteDocumentFromCollection deletes a document from a given collection', () async { final FirestoreService firestoreService = FirestoreService(firestore: fakeFirebaseFirestore!); const String collectionPath = 'collectionPath'; final CollectionReference<Map<String, dynamic>> collectionReference = fakeFirebaseFirestore!.collection(collectionPath); final DocumentReference<Map<String, dynamic>> documentReference = await collectionReference.add(data); final String documentPath = documentReference.path; await firestoreService.deleteDocumentFromCollection( collectionPath: collectionPath, documentPath: documentPath); final DocumentSnapshot<Map<String, dynamic>> documentSnapshot = await collectionReference.doc(documentPath).get(); expect(documentSnapshot.exists, false); /// Passes ✅ });- Injecting the mock Firestore object,
Test For Getting A Document's Data.
Future<DocumentSnapshot<Map<String, dynamic>>> getFromDocument( {required String collectionPath, required String documentPath}) { return firestore.collection(collectionPath).doc(documentPath).get(); }The
getFromDocumentmethod above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore, to the service. - Setting data to the document via the
fakeFirebaseFirestoreobject. - Retrieving the actual data in the document via the service's
getFromDocumentmethod. - Asserting that the data retrieved via the service is equal to the data set via
fakeFirebaseFirestore.
This is shown below:
test('getFromDocument gets data from a given document', () async { final FirestoreService firestoreService = FirestoreService(firestore: fakeFirebaseFirestore!); const String collectionPath = 'collectionPath'; const String documentPath = 'documentPath'; final DocumentReference<Map<String, dynamic>> documentReference = fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath); await documentReference.set(data); final DocumentSnapshot<Map<String, dynamic>> expectedDocumentSnapshot = await documentReference.get(); final DocumentSnapshot<Map<String, dynamic>> actualDocumentSnapshot = await firestoreService.getFromDocument( collectionPath: collectionPath, documentPath: documentPath); final Map<String, dynamic>? expectedData = expectedDocumentSnapshot.data(); final Map<String, dynamic>? actualData = actualDocumentSnapshot.data(); expect(actualData, expectedData); /// Passes ✅ });- Injecting the mock Firestore object,
Test For Setting Documents's Data.
Future<void> setDataOnDocument( {required Map<String, dynamic> data, required String collectionPath, required String documentPath}) { return firestore.collection(collectionPath).doc(documentPath).set(data); }The
setDataOnDocumentmethod above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore, to the service. - Setting data to the document via the service's
setDataOnDocumentmethod. - Retrieving the actual data in the document via the
fakeFirebaseFirestoreobject. - Asserting that the data set via the service is equal to the data retrieved via
fakeFirebaseFirestore.
This is shown below:
test('setDataOnDocument sets data on a given document', () async { final FirestoreService firestoreService = FirestoreService(firestore: fakeFirebaseFirestore!); const String collectionPath = 'collectionPath'; const String documentPath = 'documentPath'; await firestoreService.setDataOnDocument( data: data, collectionPath: collectionPath, documentPath: documentPath); final DocumentReference<Map<String, dynamic>> documentReference = fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath); final DocumentSnapshot<Map<String, dynamic>> actualDocumentSnapshot = await documentReference.get(); final Map<String, dynamic>? actualData = actualDocumentSnapshot.data(); const Map<String, dynamic> expectedData = data; expect(actualData, expectedData); /// Passes ✅ });- Injecting the mock Firestore object,
Test For Getting A Snapshot Stream From A Document.
Stream<DocumentSnapshot<Map<String, dynamic>>> getSnapshotStreamFromDocument( {required String collectionPath, required String documentPath}) { return firestore.collection(collectionPath).doc(documentPath).snapshots(); }The
getSnapshotStreamFromDocumentmethod above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore, to the service. - Setting the data to the document via the
fakeFirebaseFirestoreobject. - Retrieving the expected snapshot stream from the
fakeFirebaseFirestoreobject and the actual snapshot stream from the service'sgetSnapshotStreamFromDocumentmethod. - Getting the data from the two snapshot streams.
- Asserting that the actual data via the service is equal to the expected data via
fakeFirebaseFirestore.
This is shown below:
test( 'getSnapshotStreamFromDocument returns a stream of DocumentSnapshot containing the data set', () async { final FirestoreService firestoreService = FirestoreService(firestore: fakeFirebaseFirestore!); const String collectionPath = 'collectionPath'; const String documentPath = 'documentPath'; final DocumentReference<Map<String, dynamic>> documentReference = fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath); await documentReference.set(data); final Stream<DocumentSnapshot<Map<String, dynamic>>> expectedSnapshotStream = documentReference.snapshots(); final Stream<DocumentSnapshot<Map<String, dynamic>>> actualSnapshotStream = firestoreService.getSnapshotStreamFromDocument( collectionPath: collectionPath, documentPath: documentPath); final DocumentSnapshot<Map<String, dynamic>> expectedDocumentSnapshot = await expectedSnapshotStream.first; final DocumentSnapshot<Map<String, dynamic>> actualDocumentSnapshot = await actualSnapshotStream.first; final Map<String, dynamic>? expectedData = expectedDocumentSnapshot.data(); final Map<String, dynamic>? actualData = actualDocumentSnapshot.data(); expect(actualData, expectedData); /// Passes ✅ });- Injecting the mock Firestore object,
Test For Updating A Document's Data.
Future<void> updateDataOnDocument( {required Map<String, dynamic> data, required String collectionPath, required String documentPath}) { return firestore.collection(collectionPath).doc(documentPath).update(data); }The
updateDataOnDocumentmethod above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore, to the service. - Setting data to the document via the
fakeFirebaseFirestoreobject. - Updating the data set to the document via the service's
updateDataOnDocumentmethod. - Retrieving the data in the document via
fakeFirebaseStore. - Asserting that the updated data set via the service is equal to the expected data retrieved via
fakeFirebaseFirestore.
This is shown below:
test('updateDataOnDocument updates a given document\'s data', () async { final FirestoreService firestoreService = FirestoreService(firestore: fakeFirebaseFirestore!); const String collectionPath = 'collectionPath'; const String documentPath = 'documentPath'; final DocumentReference<Map<String, dynamic>> documentReference = fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath); await documentReference.set(data); final Map<String, dynamic> dataUpdate = {'data': '43'}; await firestoreService.updateDataOnDocument( data: dataUpdate, collectionPath: collectionPath, documentPath: documentPath); final DocumentSnapshot<Map<String, dynamic>> actualDocumentSnapshot = await documentReference.get(); final Map<String, dynamic>? actualData = actualDocumentSnapshot.data(); final Map<String, dynamic> expectedData = dataUpdate; expect(actualData, expectedData); /// Passes ✅ });- Injecting the mock Firestore object,
Complete Test File
The content of the entire test file is shown below:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
import 'package:firestore_unit_test_flutter/firestore_service.dart';
import 'package:flutter/foundation.dart';
import 'package:test/test.dart';
void main() {
group('FirestoreService', () {
FakeFirebaseFirestore? fakeFirebaseFirestore;
const Map<String, dynamic> data = {'data': '42'};
setUp(() {
fakeFirebaseFirestore = FakeFirebaseFirestore();
});
group(
'Collection Operations',
() {
test('addToCollection adds data to given collection', () async {
final FirestoreService firestoreService =
FirestoreService(firestore: fakeFirebaseFirestore!);
const String collectionPath = 'collectionPath';
await firestoreService.addToCollection(
data: data, collectionPath: collectionPath);
final List<Map<String, dynamic>> actualDataList =
(await fakeFirebaseFirestore!.collection('collectionPath').get())
.docs
.map((e) => e.data())
.toList();
expect(actualDataList, const MapListContains(data));
});
test('getFromCollection gets data from a given collection', () async {
final FirestoreService firestoreService =
FirestoreService(firestore: fakeFirebaseFirestore!);
const String collectionPath = 'collectionPath';
await fakeFirebaseFirestore!.collection(collectionPath).add(data);
final List<Map<String, dynamic>> dataList = (await firestoreService
.getFromCollection(collectionPath: collectionPath))
.docs
.map((e) => e.data())
.toList();
expect(dataList, const MapListContains(data));
});
test(
'getSnapshotStreamFromCollection returns a stream of QuerySnaphot containing the data added',
() async {
final FirestoreService firestoreService =
FirestoreService(firestore: fakeFirebaseFirestore!);
const String collectionPath = 'collectionPath';
final CollectionReference<Map<String, dynamic>> collectionReference =
fakeFirebaseFirestore!.collection(collectionPath);
await collectionReference.add(data);
final Stream<QuerySnapshot<Map<String, dynamic>>>
expectedSnapshotStream = collectionReference.snapshots();
final actualSnapshotStream = firestoreService
.getSnapshotStreamFromCollection(collectionPath: collectionPath);
final QuerySnapshot<Map<String, dynamic>> expectedQuerySnapshot =
await expectedSnapshotStream.first;
final QuerySnapshot<Map<String, dynamic>> actualQuerySnapshot =
await actualSnapshotStream.first;
final List<Map<String, dynamic>> expectedDataList =
expectedQuerySnapshot.docs.map((e) => e.data()).toList();
final List<Map<String, dynamic>> actualDataList =
actualQuerySnapshot.docs.map((e) => e.data()).toList();
expect(actualDataList, expectedDataList);
});
},
);
group('Document Operations', () {
test(
'deleteDocumentFromCollection deletes a document from a given collection',
() async {
final FirestoreService firestoreService =
FirestoreService(firestore: fakeFirebaseFirestore!);
const String collectionPath = 'collectionPath';
final CollectionReference<Map<String, dynamic>> collectionReference =
fakeFirebaseFirestore!.collection(collectionPath);
final DocumentReference<Map<String, dynamic>> documentReference =
await collectionReference.add(data);
final String documentPath = documentReference.path;
await firestoreService.deleteDocumentFromCollection(
collectionPath: collectionPath, documentPath: documentPath);
final DocumentSnapshot<Map<String, dynamic>> documentSnapshot =
await collectionReference.doc(documentPath).get();
expect(documentSnapshot.exists, false);
});
test('getFromDocument gets data from a given document', () async {
final FirestoreService firestoreService =
FirestoreService(firestore: fakeFirebaseFirestore!);
const String collectionPath = 'collectionPath';
const String documentPath = 'documentPath';
final DocumentReference<Map<String, dynamic>> documentReference =
fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);
await documentReference.set(data);
final DocumentSnapshot<Map<String, dynamic>> expectedDocumentSnapshot =
await documentReference.get();
final DocumentSnapshot<Map<String, dynamic>> actualDocumentSnapshot =
await firestoreService.getFromDocument(
collectionPath: collectionPath, documentPath: documentPath);
final Map<String, dynamic>? expectedData =
expectedDocumentSnapshot.data();
final Map<String, dynamic>? actualData = actualDocumentSnapshot.data();
expect(actualData, expectedData);
});
test('setDataOnDocument sets data on a given document', () async {
final FirestoreService firestoreService =
FirestoreService(firestore: fakeFirebaseFirestore!);
const String collectionPath = 'collectionPath';
const String documentPath = 'documentPath';
await firestoreService.setDataOnDocument(
data: data,
collectionPath: collectionPath,
documentPath: documentPath);
final DocumentReference<Map<String, dynamic>> documentReference =
fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);
final DocumentSnapshot<Map<String, dynamic>> actualDocumentSnapshot =
await documentReference.get();
final Map<String, dynamic>? actualData = actualDocumentSnapshot.data();
const Map<String, dynamic> expectedData = data;
expect(actualData, expectedData);
});
test(
'getSnapshotStreamFromDocument returns a stream of DocumentSnapshot containing the data set',
() async {
final FirestoreService firestoreService =
FirestoreService(firestore: fakeFirebaseFirestore!);
const String collectionPath = 'collectionPath';
const String documentPath = 'documentPath';
final DocumentReference<Map<String, dynamic>> documentReference =
fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);
await documentReference.set(data);
final Stream<DocumentSnapshot<Map<String, dynamic>>>
expectedSnapshotStream = documentReference.snapshots();
final Stream<DocumentSnapshot<Map<String, dynamic>>>
actualSnapshotStream =
firestoreService.getSnapshotStreamFromDocument(
collectionPath: collectionPath, documentPath: documentPath);
final DocumentSnapshot<Map<String, dynamic>> expectedDocumentSnapshot =
await expectedSnapshotStream.first;
final DocumentSnapshot<Map<String, dynamic>> actualDocumentSnapshot =
await actualSnapshotStream.first;
final Map<String, dynamic>? expectedData =
expectedDocumentSnapshot.data();
final Map<String, dynamic>? actualData = actualDocumentSnapshot.data();
expect(actualData, expectedData);
});
test('updateDataOnDocument updates a given document\'s data', () async {
final FirestoreService firestoreService =
FirestoreService(firestore: fakeFirebaseFirestore!);
const String collectionPath = 'collectionPath';
const String documentPath = 'documentPath';
final DocumentReference<Map<String, dynamic>> documentReference =
fakeFirebaseFirestore!.collection(collectionPath).doc(documentPath);
await documentReference.set(data);
final Map<String, dynamic> dataUpdate = {'data': '43'};
await firestoreService.updateDataOnDocument(
data: dataUpdate,
collectionPath: collectionPath,
documentPath: documentPath);
final DocumentSnapshot<Map<String, dynamic>> actualDocumentSnapshot =
await documentReference.get();
final Map<String, dynamic>? actualData = actualDocumentSnapshot.data();
final Map<String, dynamic> expectedData = dataUpdate;
expect(actualData, expectedData);
});
});
});
}
class MapListContains extends Matcher {
final Map<dynamic, dynamic> _expected;
const MapListContains(this._expected);
@override
Description describe(Description description) {
return description.add('contains ').addDescriptionOf(_expected);
}
@override
bool matches(dynamic item, Map matchState) {
if (item is List<Map>) {
return item.any((element) => mapEquals(element, _expected));
}
return false;
}
}
Conclusion
This article demonstrates how to unit test Firebase Firestore operations involving Collections and Documents. It also shows how to use a custom Matcher in test assertions.
The complete code can be found on GitHub.



