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 theCollectionReference
andDocumentReference
.For instance, we want the initial state of the database to have an object
{answer: 42}
written to a documentlifeTheUniverseAndEverything
in 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
FakeFirebaseFirestore
object can be gotten by calling the regular read operations on theCollectionReference
andDocumentReference
.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:
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 theelement
object. And themapData
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 themapEquals
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 theMatcher
abstract class and overrides thedescribe
method and thematches
method. The assertion logic is implemented in thematches
method in theMapListContains
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
addToCollection
method. - Retrieving the actual data list in the collection via
fakeFirebaseFirestore
. - Asserting that the actual data list contains the added data using the
MapListContains
matcher.
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
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:
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
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'sgetSnapshotStreamFromCollection
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:
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
deleteDocumentFromCollection
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. - Deleting the document at the collection via the service's
deleteDocumentFromCollection
method. - 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
getFromDocument
method above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore
, to the service. - Setting data to the document via the
fakeFirebaseFirestore
object. - Retrieving the actual data in the document via the service's
getFromDocument
method. - 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
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
setDataOnDocument
method. - Retrieving the actual data in the document via the
fakeFirebaseFirestore
object. - 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
getSnapshotStreamFromDocument
method above can be tested by:- Injecting the mock Firestore object,
fakeFirebaseFirestore
, to the service. - Setting the data to the document via the
fakeFirebaseFirestore
object. - Retrieving the expected snapshot stream from the
fakeFirebaseFirestore
object and the actual snapshot stream from the service'sgetSnapshotStreamFromDocument
method. - 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
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 ✅ });
- 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.