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

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 FakeFirebaseFirestore object can be set up by calling the regular write operations on the CollectionReference and DocumentReference.
For instance, we want the initial state of the database to have an object {answer: 42} written to a document lifeTheUniverseAndEverything in collection answers, 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 FakeFirebaseFirestore object can be gotten by calling the regular read operations on the CollectionReference and DocumentReference.
Now, to get the final state of the FakeFirebaseFirestore object 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 expect method 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 contains matcher 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:
```dart
final List<Map<String, dynamic>> mapList = [
{'data': 42}
];
final mapData = {'data': 42};
expect(mapList, contains(mapData)); /// Fails ❌
This is because the contains matcher for Lists is implemented this way:
return item.contains(_expected);
which in turn uses the contains method 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 e object is the same object as the element object. And the mapData object 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 mapData object by using the mapEquals function 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 mapEquals compares 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 MapListContains class extends the Matcher abstract class and overrides the describe method and the matches method. The assertion logic is implemented in the matches method in the MapListContains class.
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 addToCollection method 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 ✅
});
Test For Getting Data From A Collection.
Future<QuerySnapshot<Map<String, dynamic>>> getFromCollection() { return firestore.collection('collectionPath').get(); }
The `getFromCollection` method above can be tested by:
- Injecting the mock Firestore object, `fakeFirebaseFirestore`, to the service.
- Adding the data to the collection via the `fakeFirebaseFirestore` object.
- Retrieving the actual data list in the collection via the service's `getFromCollection` method.
- Asserting that the data list contains the added data using the `MapListContains` matcher.
This is shown below:
```dart
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 ✅
});
Test For Getting A Snapshot Stream From A Collection.
Stream<QuerySnapshot<Map<String, dynamic>>> getSnapshotStreamFromCollection( {required String collectionPath}) { return firestore.collection(collectionPath).snapshots(); }
The `getSnapshotStreamFromCollection` method above can be tested by:
- Injecting the mock Firestore object, `fakeFirebaseFirestore`, to the service
- Adding the data to the collection via the `fakeFirebaseFirestore` object.
- Retrieving the expected snapshot stream from the `fakeFirebaseFirestore` object and the actual snapshot stream from the service's `getSnapshotStreamFromCollection` method.
- 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:
```dart
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 ✅
});
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 deleteDocumentFromCollection method 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 ✅
});
- #### *Test For Getting A Document's Data.*
```dart
Future<DocumentSnapshot<Map<String, dynamic>>> getFromDocument(
{required String collectionPath, required String documentPath}) {
return firestore.collection(collectionPath).doc(documentPath).get();
}
The getFromDocument method 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: ```dart 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 ✅
});
- #### *Test For Setting Documents's Data.*
```dart
Future<void> setDataOnDocument(
{required Map<String, dynamic> data,
required String collectionPath,
required String documentPath}) {
return firestore.collection(collectionPath).doc(documentPath).set(data);
}
The setDataOnDocument method 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 ✅
});
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 getSnapshotStreamFromDocument method 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 ✅
});
- #### *Test For Updating A Document's Data.*
```dart
Future<void> updateDataOnDocument(
{required Map<String, dynamic> data,
required String collectionPath,
required String documentPath}) {
return firestore.collection(collectionPath).doc(documentPath).update(data);
}
The updateDataOnDocument method above can be tested by: - Injecting the mock Firestore object, fakeFirebaseFirestore, to the service. - Setting data to the document via the fakeFirebaseFirestore object. - Updating the data set to the document via the service's updateDataOnDocument method. - 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 ✅
});
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.
About the author
- 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.



