sbatson5 / firestore-jest-mock Goto Github PK
View Code? Open in Web Editor NEWJest Helper library for mocking Cloud Firestore
Home Page: https://www.npmjs.com/package/firestore-jest-mock
Jest Helper library for mocking Cloud Firestore
Home Page: https://www.npmjs.com/package/firestore-jest-mock
Queries return 0 docs
The following minimal example is built from help docs & tests in this repo:
import { mockFirebase } from "firestore-jest-mock";
mockFirebase({
database: {
users: [
{ id: "abc123", name: "Homer Simpson" },
{ id: "abc456", name: "Lisa Simpson" },
],
posts: [{ id: "123abc", title: "Really cool title" }],
},
});
const firebase = require('firebase');
firebase.initializeApp({
apiKey: '### FIREBASE API KEY ###',
authDomain: '### FIREBASE AUTH DOMAIN ###',
projectId: '### CLOUD FIRESTORE PROJECT ID ###'
});
const db = firebase.firestore();
test('testing stuff', () => {
db.collection('users')
.get()
.then((userDocs: any) => {
// write assertions here
expect(userDocs.docs.length).toBe(2);
});
})
test should pass
Fails with recieved: 0, expected 2.
Struggling to get a test passing
const { mockFirebase } = require("firestore-jest-mock");
mockFirebase({
database: {
users: [
{ id: "abc123", name: "Homer Simpson" },
{ id: "abc456", name: "Lisa Simpson" },
],
posts: [{ id: "123abc", title: "Really cool title" }],
},
});
const { mockCollection } = require("firestore-jest-mock/mocks/firestore");
test("testing stuff", () => {
const admin = require("firebase-admin"); // or import firebase from 'firebase';
admin.initializeApp();
const db = admin.firestore();
return db
.collection("users")
.get()
.then((userDocs) => {
console.log(userDocs);
expect(mockCollection).toHaveBeenCalledWith("users");
expect(userDocs.docs[0].data().name).toEqual("Homer Simpson");
});
});
Test pass
Fails, firestore not mocked
Currently the Batch Commit returns undefined
, to not break systems that use the WriteResult[]
response an array should be returned instead
const result = await batch.commit();
logger.info( 'Records Saved:', result.length );
Without needing to mock the full WriteResult
object an empty array would make sure things like length
, forEach
and map
won't break.
commit() {
mockBatchCommit(...arguments);
return Promise.resolve([]);
},
@google-cloud/firestore SDK is an alternative to firebase
and firebase-admin
to interact with Firestore. The exported Firestore
class is very similar (probably identical but Im not sure) to firebase.firestore
.
I use firestore-jest-mock
in one of my project with @google-cloud/firestore
.
I added this file:
// firestore.mock
import { FakeFirestore } from 'firestore-jest-mock';
type Overrides = {
database?: Record<string, unknown>;
};
const firestoreMock = (overrides: Overrides) => {
class Firestore extends FakeFirestore {
constructor() {
super(overrides.database);
}
}
return {
FieldValue: FakeFirestore.FieldValue,
Firestore,
};
};
export const mockFirestore = (overrides = {}): void => {
jest.doMock('@google-cloud/firestore', () => firestoreMock(overrides));
};
And I use mockFirestore
exactly as the mockFirebase
of firestore-jest-mock
:
import { mockFirestore } from './firestore.mock';
import { mockCollection, mockDoc, mockSet } from 'firestore-jest-mock/mocks/firestore';
const getMockedFirestore = async () => {
mockFirestore();
const { Firestore } = await import('@google-cloud/firestore');
return new Firestore({
projectId: process.env.GCP_PROJECT_NAME,
});
};
describe('restaurants', () => {
it('creates the restaurant if it does not exist', async () => {
const restaurantId = 15;
const firestore = await getMockedFirestore();
await firestore.collection('restaurants').doc(restaurantId.toString()).set({ restaurantId });
expect(mockCollection).toHaveBeenCalledWith('restaurants');
expect(mockDoc).toHaveBeenCalledWith(restaurantId.toString());
expect(mockSet).toHaveBeenCalledWith({ restaurantId });
});
});
firestore-jest-mock
provides a very useful means to test applications that use Firestore
. The Firestore SDK is complicated to mock and the work done in firestore-jest-mock
could also help the users of @google-cloud/firestore
as well as the ones of firebase
.
I propose a simple implementation to expose the FakeFirestore
according to the signature of @google-cloud/firestore
in the linked pull request.
In the @google-cloud/firestore compatibility example:
expect(userDocs[0].name).toEqual('Homer Simpson');
fails with:
โ testing stuff
expect(received).toEqual(expected) // deep equality
Expected: "Homer Simpson"
Received: undefined
25 | console.error(userDocs);
26 | expect(mockCollection).toHaveBeenCalledWith("users");
> 27 | expect(userDocs.docs[0].name).toEqual("Homer Simpson");
| ^
28 | });
29 | });
30 |
Change to:
expect(userDocs.docs[0].data().name).toEqual("Homer Simpson");
or get all docs first, via map()
:
const result = userDocs.docs.map((doc) => doc.data());
Does this library support @react-native-firebase/firestore?
I followed the setup instructions in the README for a react native project; however, I get the following error:
Error: You attempted to use a firebase module that's not installed natively on your iOS project by calling firebase.app().
This leads me to believe that the library does not support @react-native-firebase.
It would be very helpful to apply this mocking to react native projects!
Hi!
I have an issue with validating of collection type that doc
method was called.
Is any way to get info from mockDoc
about collection types that been triggered?
For example:
I get doc
from collection named users
so then I testing function I should know from mockDoc
that it has been called from users
collection exactly.
Now I know how many times function has been called or what the arguments have been used.
Maybe I can get a context of called function from mockDoc
to someway define the collection type?
I'm using jest v28.1.3
and firestore-jest-mock 0.18.0
Hello, firstly thank you for creating this wonderful library! I've been using it over the last week to write some very useful tests for my project.
I am not sure if this is a feature request, support question or bug!
I am wondering if there are any good examples of how to setup different database data for tests within one code file? I am finding that I have to use requires to import everything that uses firebase or else they get imported before the mocking happens and it doesn't work.
This seems to also mean that only the first mockGoogleCloudFirestore() is used within my tests.
Is there a way of storing an instance of mockGoogleCloudFirestore and adjusting the database? For now I am using mutable: true and managing the data between tests, but this is a lot of what feels unnecessary work.
Ideally I would also like to remove the need for using requires, as this is breaking my typescript and makes the tests harder to manage (if an imported file in tests starts using firebase then it has to be moved into a require or seemingly unrelated tests will start failing).
I am hoping that I am just missing something obvious here, but if not I would love a way of adjusting the data. I have provided a failing example test below:
import { mockGoogleCloudFirestore } from 'firestore-jest-mock';
describe('some tests', () => {
it('Should do the first thing', async () => {
mockGoogleCloudFirestore({
database: {
test: [{ id: '1', someField: true }],
},
});
const { Firestore } = require('@google-cloud/firestore');
const firestore = new Firestore();
const results = await firestore.collection('test').get();
expect(results.docs[0].data().someField).toEqual(true); // WORKS
});
it('Should do the second thing', async () => {
mockGoogleCloudFirestore({
database: {
test: [{ id: '1', someField: false }],
},
});
const { Firestore } = require('@google-cloud/firestore');
const firestore = new Firestore();
const results = await firestore.collection('test').get();
expect(results.docs[0].data().someField).toEqual(false); // FAILS
});
});
npm version: v8.12.1
Node version: v18.0.4
"firebase-functions-test": "2.4.0",
"@google-cloud/firestore": "6.0.0",
I've tried to use mockFirebase
, however it always logs "Module firebase not found, mocking skipped.".
The error comes from here:
It tries to require.resolve('firebase')
, however it seems like in Firebase 9 this is not possible anymore (I'm using e.g. require('firebase/firestore')
so the mock should likely use similar imports).
Create a project with firebase >= 9. Call mockFirebase()
.
See this repo for a minimal example: https://github.com/Otto-AA/firestore-mock-bug-demo
It should mock firebase.
It does not find the firebase module.
Trying to test firebase.auth(), but getting an error. I've cloned this repo and the same tests seem to run just fine, but running in my actual code and using the npm package, it fails. Really not sure what it is I'm doing wrong, other than package versions, I'm following the same pattern as in this repo's tests, just inside my own solution. If this is not a defect, and something I'm doing wrong, I apologize for marking it as such.
test('signInUser', async () => {
expect.assertions(1)
mockFirebase({
database: {
users: [
{ id: 'abc123', name: 'Homer Simpson' },
{ id: 'abc456', name: 'Lisa Simpson' },
],
},
})
const firebase = require('firebase')
firebase.initializeApp(firebaseConfig)
await firebase.auth().signInWithEmailAndPassword('sam', 'hill')
expect(mockSignInWithEmailAndPassword).toHaveBeenCalledWith('sam', 'hill')
})
The mock for signInWithEmailAndPassword should be called
TypeError: Cannot set property 'sendEmailVerification' of undefined
35 | firebase.initializeApp(firebaseConfig)
36 |
> 37 | await firebase.auth().signInWithEmailAndPassword('sam', 'hill')
| ^
38 | expect(mockSignInWithEmailAndPassword).toHaveBeenCalledWith('sam', 'hill')
39 | })
40 | })
at new FakeAuth (node_modules/firestore-jest-mock/mocks/auth.js:10:39)
at Object.auth (node_modules/firestore-jest-mock/mocks/firebase.js:14:14)
at Object.<anonymous> (tests/unit/services/authorization/index.spec.js:37:20)
I propose we add some way for users to assert access to specific document or collection paths.
We should be able to assert transaction or batched writes like this:
// The update call:
expect(mockUpdateTransaction).toHaveBeenCalledWith(
// The document ref:
expect.toHaveFirestorePath(`accounts/${accountId}/users/${userId}`),
// The data:
{ ... }
);
Here we assert that the Firestore API was called with the expected parameters while asserting that one of those parameters defined a specific Firestore document path.
Standalone reads and writes are a bit trickier, since calling ref.update({ ... })
doesn't presently inform mockUpdate
about the path on which the access was made. I don't have any good ideas for that yet, but at the very least we should have some matchers for our mock DocumentReference
or CollectionReference
types.
Since we now have subcollection support as of #35, users now have a problem when trying to assert access to a specific document path. I do not mean access permissions, those are handled by Firestore rules and beyond the scope of firestore-jest-mock. Presently, the normal way to assert correct document access is by using the variations of the following logical argument:
mockDoc
was called after mockCollection
, using either expect(...).toHaveBeenCalledAfter
or expect(...).not.toHaveBeenCalledBefore
, preferably both.mockCollection
was called with the correct collection ID c
, using expect(...).toHaveBeenCalledWith
, or more preferably expect(...).toHaveBeenNthCalledWith
.mockDoc
was called with the correct document ID d
, using expect(...).toHaveBeenCalledWith
, or more preferably expect(...).toHaveBeenNthCalledWith
.c/d
.This works well enough when the unit under test only deals with one Firestore document, and calls these methods exactly once each. But that is often not the case. Nested subcollections break the assumption that mockCollection
was called only before mockDoc
, and therefore our conclusion no longer holds true in every case. We now need to factor in which call to mockDoc
or mockCollection
we're asserting against, and then any reordering of accesses breaks unit tests, even when such access has only to do with the construction of DocumentReference
s.
Consider the following operation on two account-scoped documents that each represent an account member:
const accountA = ...;
const accountB = ...;
const userId = ...;
const userRef = db
.collection("accounts")
.doc(accountA)
.collection("users")
.doc(userId);
const otherUserRef = db
.collection("accounts")
.doc(accountB)
.collection("users")
.doc(userId);
A common reason for preparing DocumentReference
s like these is to use them in a transaction
or a batch
write operation, sometimes both:
await db.runTransaction(async transaction => {
const user = await transaction.get(userRef);
...
transaction.update(otherUserRef, { ... });
});
As of today, we have no way to assert directly that "the document at path `accounts/${accountId}/users/${userId}`
was updated with { this data }
". Today's methods, which can easily confuse assertions on userRef
with assertions on otherUserRef
, rely on the following assertions in Jest:
mockCollection
was called four times, twice with the argument "accounts"
and twice with the argument "users"
. We assert this using four toHaveBeenNthCalledWith
assertions, and one toHaveBeenCalledTimes
assertion for good measure.mockDoc
was called four times, once with the argument accountA
, once with the argument accountB
, and twice with the argument userId
. We assert this in the same verbose manner as we did mockCollection
.mockUpdateTransaction
was called once with the first argument being an instance of FakeFirestore.DocumentReference
and the second being { the data }
. To assert more than this requires reconstructing the same document reference using the mocked Firestore database (which may involve an import
or a require
). This is a nontrivial operation.We cannot clearly assert the order of calls to mockCollection
and mockDoc
without doing a fancy dance about which call with which arguments came before which other call with which arguments, which will always be complicated by the fact that we call collection("accounts")
twice. We may simplify this call, but the issue remains about the ordering of the other calls. Test cases quickly become very bulky and difficult to maintain.
Asserting specific document paths can be done with a simple Jest matcher!
FakeFirestore.DocumentReference
has a path
property which contains a string similar to the one canonical to Firestore.DocumentReference
. In theory, we all we need to do to assert that a transaction.update
call occurred on the correct path is to assert that the call's first argument has a path
property and that its value is the expected document path.
We may define the matcher with something like the following TypeScript code:
function toHaveFirestorePath(
this: jest.MatcherUtils,
ref: { path: string },
path: string
): jest.CustomMatcherResult {
if (typeof path !== "string") {
throw new Error(`Expected 'path' to be a string. Got ${typeof path}: ${path}`);
}
const pass =
ref && // truthy
typeof ref === "object" && // is an object
"path" in ref && // has a "path" property
typeof ref.path === "string" && // ref.path is a string
ref.path.startsWith(path); // ref.path is at least a child of the expected path
// The message only appears when the test fails (whether by passing when it was expected not to
// or by not passing when it was expected to)
return {
message: () => `Expected '${path}' ${pass ? "NOT" : ""}to be '${ref.path}'`,
pass
};
}
This code uses the path
property of FakeFirestore.DocumentReference
or FakeFirestore.CollectionReference
to check that the value under test is a child of the provided path.
We use the matcher in test cases like so:
// the unit under test
async function doTheThing() {
const ref = db.collection("accounts").doc("accountA").collection("users").doc("userA");
await db.runTransaction(async transaction => {
...
await transaction.get(ref);
...
});
}
// the test
beforeEach(async () => {
await doTheThing();
});
test("reads from account A", () => {
expect(mockGetTransaction).toHaveBeenCalledWith(
expect.toHaveFirestorePath(`accounts/accountA`)
);
});
With one simple assertion, we can prove that the unit accessed some document under accounts/accountA
. It is now trivial to assert other important parts of the Firestore call, such as:
Other matchers may be written to handle more granular or more specific cases as needed.
FakeFirestore.DocumentReference#path
The canonical Firestore.DocumentReference
object has a path
property, but in designing FakeFirestore.DocumentReference
I did not consider the structure of the canon version. This may break some code that relies on the value of that path
property. We should update our implementation to match Firestore's.
#102 makes document paths more closely match Firestore's implementation to the best of my knowledge.
The matcher described in this proposal only extend Jest's present ability to assert properties of arguments to mock function calls. As far as I am aware, Jest does not have an easy way to assert properties of objects on which mock methods were called. Some restructuring may be necessary to permit that capability in a similar fashion. Suggestions would be appreciated.
EDIT: IDEA!! IIRC, Firestore canonically returns a document ref or document snapshot as the result of a write operation. We might be able to assert the path of that return value in a Jest matcher.
When you fetch a specific document with full path, it doesn't find it from the mock database.
mockFirebase({
database: {
'mycol/id/mysubcol': [
{ id: 'subId' },
],
})
const doc = await lib.db
.doc(`mycol/id/mysubcol/subId`)
.get()
But this one does work:
const doc = await lib.db
.collection(`mycol/id/mysubcol`)
.doc('subId')
.get()
The document from the database.
No result.
When testing a query, the docs that are returned are missing the ref
property so when I test mockSet or mockUpdate, the ref
is undefined.
Basically, we have some integration tests that run the triggers against the emulator, but I wanted to have those running against the mocked db instead. Trying to edit a mocked db document doesn't seem to trigger those, the updates are happening correctly but the internal code in the trigger never executes.
I was trying to call the function we have on the trigger, some onUpdate
for example, like onUserUpdate
is one we want to test, but to directly call those we need to provide the snap
and context
objects, the snap
is defined as a Change which unfortunately I was not able to mock, but the context seems fine as long as you have an object with an EventType it accepts, but complains about trying to parse the Snap as a JSON:|
Qs:
Once again thank you very much for the hard work done here.
I am trying to set up the mockFirebase
in our expo project, however, I get the error:
console.info
Module firebase-admin not found, mocking skipped.
at mockModuleIfFound (node_modules/firestore-jest-mock/mocks/firebase.js:49:13)
Before opening an issue, do I need to install the firebase-admin to be able to use this library in the client?
Installing the firebase admin does make the mocking work, but is it really required? Also, do I need to import firebase twice, on top to init the app, and inside the test block as well, any ideas about what is wrong here?
I am using this library to unit test my cloud functions, which (evidently ?) do not depend on ('firebase'), however, wherever I am using firestore-jest-mock, I get a console.info
Since my project is going to become much bigger, this would mean that I can not use the library as I need to keep the test-restuls as clean as possible! Looking into the code, I couldn't find an "easy toggle" to turn this output off. What can I do?
Would prefer import admin from 'firebase-admin'
syntax instead of require('firebase-admin')
Including sdk with require('firebase-admin')
syntax seems mandatory (regarding docs, sample and my own experience) to make firestore mock work.
Otherwise, the firestore mock is not initialised properly and the tests crash at the first collection call.
As moderns Typescript coders, my team and I would prefer a proper import admin from 'firebase-admin')
style of include.
Is there a way to initialize the test, mocks etc properly without that ugly require
thing ?
Even adding a jest.mock(admin.firestore...)
or something would be more acceptable to us.
You guys did a great job, by the way ;-)
I can provide test sample if needed, but i gess my question is quite self explanatory.
Firestore has .onSnapshot()
which is an alternative to .get()
on a collection. It is used to get realtime updates from the DB. This is currently not handled by this mock.
firestore
.collection( Collections.history )
.onSnapshot( ( snapshot ) => {
setHistory(snapshot.docs.map(doc => doc.data() as IHistory));
} );
My code uses quite a few of these to sync my UI with the DB, so to test them I need to be able to mock them and this library is the best option to mock Firebase.
To mock this we should be able to use the same logic as the .get()
instead of as a Promise returning the query results as a function callback.
Very helpful library!
It would be crazy awesome if it had types or was migrated to typescript.
Almost all the google sdks are typed now, or are migrating to typescript itself.
I have my firestore services split into their own files to make mocking the responses the UI needs easier. I have been trying to use this library but can't seem to get these separated files to use the mocked firebase. If I use the db calls directly as shown in the example tests the mocks are called but that doesn't test the files making the calls.
service.js
import firebase from 'firebase';
const firestore = firebase.firestore();
export const getData = () => firestore.collection('data').orderBy('value');
service.test.js
import { getData } from './service';
//works
it('should get ordered data', () => {
const firebase = require('firebase');
const db = firebase.firestore();
db.collection('data').orderBy('value');
expect(mockCollection).toHaveBeenNthCalledWith(1, 'data');
expect(mockOrderBy).toHaveBeenNthCalledWith(1, 'value');
});
//doesn't work
it('should get ordered data', () => {
getData();
expect(mockCollection).toHaveBeenNthCalledWith(1, 'data');
expect(mockOrderBy).toHaveBeenNthCalledWith(1, 'value');
});
N/A
Making calls to delete()
a document in a collection does not remove the document.
Looking at /node_modules/firestore-jest-mock/mocks/firestore.js
, the implementation for document deletion seems to be missing:
delete() {
mockDelete(...arguments);
return Promise.resolve();
}
I found some bugs.
d.setSeconds(this.seconds * 1000);
. Also, using setSeconds()
, setMilliseconds()
etc. comes with some problems - I'll get to this later.d.getTime()
instead of d.getMilliseconds()
.nanoseconds
part and once as a fraction of the seconds
part. It is not explicitly specified in the documentation but I think the intent is that both seconds
and nanoseconds
would be integers. The implementation from the actual Firebase SDK seems correct to me.const now = new Date(); // now = Sun May 02 2021 21:12:49 GMT+0300 (EEST)
const nowFromMilliseconds = new Date(0); // nowFromMilliseconds = Thu Jan 01 1970 02:00:00 GMT+0200 (EET)
nowFromMilliseconds.setMilliseconds(now.getTime()); // nowFromMilliseconds = Sun May 02 2021 20:12:49 GMT+0300 (EEST)
As you can see, nowFromMilliseconds
is trailing now
by one hour. I've been reading the MDN Web Docs and language specs but I'm still not entirely clear about the mechanics of this. But I think the one hour discrepancy comes from the nowFromMilliseconds
starting at a different time zone than where it's landing on when setting the milliseconds. Nevertheless, I think this headache could be prevented by just passing the milliseconds to the Date
constructor and not setting milliseconds separately.
Firebase Auth sign out is not currently part of the available mocks to assert on
โimportโย โ{โย โmockSignOutโย โ}โย โfromโย โ'firestore-jest-mock/mocks/auth'โ;
it('signs out', async () => {
const firebase = require('firebase');
const auth = firebase.auth();
await auth.signOut();
expect(mockSignOut).toHaveBeenCalled():
});
This is not currently part of the available mocks and would be helpful just like the sign in
Are plans to support Firebase Cloud Messaging?
I'm trying to mock Firestore data that's a few layers deep in the collection/document tree. In my testing so far, the mocked get()
function doesn't resolve for more than 5 seconds, and then Jest times out.
My mock tree is laid out like so:
mockFirebase({
database: {
accounts: [ // collection, right?
{
id: testAcctId,
conversations: [ // subcollection, right?
{ id: testConversationId, participants: ["someone", "someone-else"] }
],
userPermissions: [ // another subcollection, right?
{ id: "someone", level: "normal" },
{ id: "someone-else", level: "admin" }
]
}
]
}
});
My coverage report says the test function stops at this call:
const { firestore } = require("../my/firestoreSetupFile"); // We reexport require("firebase-admin") from here
const userPermissionsSnapshot = await firestore
.collection("accounts")
.doc(accountId)
.collection("userPermissions")
.doc(userId)
.get(); // We hang here ~5 seconds before timeout. What's up with that?
The way I understand it, and based on the README, it seems that firestore-jest-mock
interprets arrays of objects with the id
attribute to denote subcollections of documents. Am I incorrect in that assumption? And could it be that it only assumes that collections are at the top level? If not, do I have the syntax right to mock a database with 2+ layers of subcollections? I have more, but it is at this point that my tests fail to run properly, and I'm not a little confused.
I'm using Jest 25.5.3
In firebase a Query
, CollectionReference
and DocumentReference
can use the withConverter
method, this is not currently part of the mocks or faked query, document or collection
const userConverter = {
toFirestore({ permission }) {
return { permission };
},
fromFirestore(snapshot, options) {
const { permission } = snapshot.data(options);
return new User(permission);
}
};
firestore.collection('users').withConverter(userConverter).get();
The above example throws an error firestore.collection(...).withConverter is not a function
as there is no withConverter
implemented into the faked query, document or collection. I use these converters to avoid duplication of conversions but cannot use if testing with this library.
The method docChanges
from buildQuerySnapShot
https://github.com/Upstatement/firestore-jest-mock/blob/cc27ce8aea7549febca5d6ad726aa4403d478c2d/mocks/helpers/buildQuerySnapShot.js#L19
returns an object.
But indeed this methods returns an array
https://github.com/googleapis/nodejs-firestore/blob/c7d67fb749aaf76050c08d29b4c6fca28ec9f5ce/types/firestore.d.ts#L1467
Consequently, my tests fail when using this method.
Try to test a code with usage of docChanges
in a callback from onSnapshot
.
Exemple:
firestore()
.collection(path)
.onSnapshot(
(querySnapshot) => {
const updates = querySnapshot
.docChanges()
.map((docChange) => ({
ticket: docChange.doc.data(),
type: docChange.type,
}));
storeApi.dispatch(updateStoredTickets(updates));
},
);
The test trigger no error
Testing the code makes it crash
14.15.5
return an empty array
From the readme it sounds like I should be able to do this:
import firebase from "firebase";
const { mockFirebase } = require("firestore-jest-mock");
describe("Database", () => {
mockFirebase({
database: {
users: [
{ id: "abc123", name: "Homer Simpson" },
{ id: "abc456", name: "Lisa Simpson" },
],
posts: [{ id: "123abc", title: "Really cool title" }],
},
});
const db = firebase.firestore();
it("can do something", () => {...});
});
However, the line const db = firebase.firestore();
fails with FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app)
.
I should be able to initialize the DB.
Currently, batch
logic is baked into the base FakeFirestore
object, which holds the logic for collections and querying. However, WriteBatch
is technically it's own object and when you call db.batch()
you get an instance of a Batch. We should have a FakeBatch
object that better tests batch logic and moves some of the logic out of FakeFirestore
API documentation: https://firebase.google.com/docs/reference/js/firebase.firestore.WriteBatch
The API exposed by this library shouldn't change and everything should be imported through firestore-jest-mock/mocks/firestore
-- but a Batch should handle its own logic.
const mockCommit = jest.fn();
class FakeWriteBatch {
commit() {
mockCommit(...arguments)
}
// ...etc
}
Easier to maintain code and adds better test coverage (as we don't currently support everything a batch can do).
Now that FakeFirestore.FieldValue
works, my unit tests can progress a little bit more. I'm now running into trouble where subcollection access matters.
I have code that access the database like so:
const snap = await firestore
.collection("accounts")
.doc(accountId)
.collection("userPermissions")
.doc(userId)
.get();
This returns a snapshot whose document doesn't exist. (That is, the object returned by the mock is simply { exists: false }
) This needs to not be the case.
I have my firestore mock declared like so:
mockFirebase({
database: {
accounts: [ // Root-level collection
{ // A document
id: "testAcct",
conversations: [ // Subcollection, right?
{ id: "testConvo", participants: ["right-user", "admin-user"] },
],
userPermissions: [ // Subcollection, right??
{ id: "right-user", level: "normal" },
{ id: "admin-user", level: "admin" },
{ id: "owner", level: "super" },
]
},
]
}
});
Am I missing something here? Is this my bad, or does the package not yet support mocking subcollections?
I should note that I see no mention in the package about subcollections, so I can only assume based on the root case that this is how subcollections are meant to be represented.
To further edge the point, I threw a test case on the package, which sadly fails:
const db = new FakeFirestore({
characters: [
{ id: 'homer', name: 'Homer', occupation: 'technician' },
{ id: 'krusty', name: 'Krusty', occupation: 'clown' },
{ // I added this document only
id: 'bob',
name: 'Bob',
occupation: 'repairman',
family: [ // I see this as a subcollection. Am I wrong, or is the mock?
{ id: 'thing1', name: 'Thing 1', relation: 'pet' },
{ id: 'thing2', name: 'Thing 2', relation: 'pet' },
{ id: 'deborah', name: 'Deborah', relation: 'wife' },
],
},
],
});
test('it can fetch records from subcollections', async () => {
expect.assertions(2);
const record = await db
.collection('characters')
.doc('bob')
.collection('family')
.doc('thing1')
.get();
expect(record.exists).toBe(true); // Expected: true, Received: false
expect(record.data()).toHaveProperty('name', 'Thing 1');
});
firestore-jest-mock 0.3.0
I'm not quite sure what class is meant to mock over firebase.firestore (is that FakeFirestore
?), but whatever it is doesn't seem to declare FieldValue
.
npm install --save-dev jest firestore-jest-mock@latest
firebase.firestore.FieldValue
. In my case, I have a file that exports an object with a few Firebase helper types:module.exports = {
firestore: firebase.firestore(),
Timestamp: firebase.firestore.Timestamp,
TIMESTAMP: firebase.firestore.FieldValue.serverTimestamp(),
increment: firebase.firestore.FieldValue.increment,
arrayUnion: firebase.firestore.FieldValue.arrayUnion,
arrayRemove: firebase.firestore.FieldValue.arrayRemove
};
mockFirebase
in tests, before importing source files under test.const { mockFirebase } = require("firestore-jest-mock");
mockFirebase({
database: {
...
}
});
jest
My tests should run, and fail for different reasons (failed assertions, etc).
No tests ran, and this error appeared in the console:
TypeError: Cannot read property 'serverTimestamp' of undefined
7 | firestore: firebase.firestore(),
8 | Timestamp: firebase.firestore.Timestamp,
> 9 | TIMESTAMP: firebase.firestore.FieldValue.serverTimestamp(),
| ^
10 | increment: firebase.firestore.FieldValue.increment,
11 | arrayUnion: firebase.firestore.FieldValue.arrayUnion,
12 | arrayRemove: firebase.firestore.FieldValue.arrayRemove,
Transaction in @google-coud/firestore has create method.
It should be implemented.
Try to use transaction.create.
Document should be created or fail the transaction if it exists.
TypeError: t.create is not a function
It seems DocumentReference.create()
is not implemented yet.
TypeError: this.db.collection(...).doc(...).create is not a function
I would like to test create()
function as I actually do with set()
function.
Actual
const { mockSet } = require('firestore-jest-mock/mocks/firestore');
test('testing stuff', () => {
const firebase = require('firebase'); // or import firebase from 'firebase';
const db = firebase.firestore();
const data = {property1: 'value1', property2: 'value2'};
return db
.collection('users')
.doc('id')
.set(data)
.then(doc => {
expect(mockSet).toHaveBeenCalledWith(data);
// Write other assertions here
});
});
Expected
const { mockCreate } = require('firestore-jest-mock/mocks/firestore');
test('testing stuff', () => {
const firebase = require('firebase'); // or import firebase from 'firebase';
const db = firebase.firestore();
const data = {property1: 'value1', property2: 'value2'};
return db
.collection('users')
.doc('id')
.create(data)
.then(doc => {
expect(mockCreate).toHaveBeenCalledWith(data);
// Write other assertions here
});
});
DocumentReference.create()
allow us to create a new document only if the documentReference does not exist yet. I actually use it in my application, but sadly can't manage to test it.
I'm trying to use this with firebase-admin, I have a mockFirebase
with the database on it on the first describe of my tests, then with a beforeEach I call
beforeEach(() => {
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.FIREBASE_PROJECT_ID,
privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
}),
databaseURL: process.env.FIREBASE_DATABASE_URL,
})
})
For some reason, when my function tries to call firebase, I get the following error:
Unable to detect a Project Id in the current environment.
To learn more about authentication and Google APIs, visit:
https://cloud.google.com/docs/authentication/getting-started
at node_modules/google-gax/node_modules/google-auth-library/build/src/auth/googleauth.js:87:31
Caused by: Error:
at CollectionReference._get (node_modules/@google-cloud/firestore/build/src/reference.js:1449:23)
at CollectionReference.get (node_modules/@google-cloud/firestore/build/src/reference.js:1438:21)
Any ideas of what I'm missing for my firebase to mock properly?
I added a function for backing up Firestore database, and now all my tests that use firestore-jest-mock
fail due to this error:
TypeError: Cannot read property 'FirestoreAdminClient' of undefined
1 | import * as admin from 'firebase-admin'
2 | import * as functions from 'firebase-functions'
> 3 | const client = new admin.firestore.v1.FirestoreAdminClient()
The function uses admin.firestore.v1.FirestoreAdminClient
type, which doesn't seem to exist in firestore-jest-mock
. Any ideas how to fix this?
It would be helpful to have a github link on https://www.npmjs.com/package/firestore-jest-mock
I had a problem and wanted to checkout the source code
But there was no link :-(
Fortunately you have released lots of code \o/ and I was able to look at your other projects on npmjs, find your github page and then found the firebase code :-)
Maybe the missing link is a mistake ?
Thanks :-)
I have a query that uses select and is failing with the error above, how do I implement this?. Thanks!
await firestore
.collection('some')
.doc(id)
.where('timestamp', '>=', since)
.select('field1, 'field2')
.get()
While it is useful for users to be able to assert that specific functions are called with the parameters they expect, it may be useful for firestore-jest-mock to make its own assertions that ensure consistency with Firestore's API. For example, the transaction mock doesn't assert that read options, if provided, are the last argument of Transaction
's variadic getAll
method. It would be easy to accidentally pass that object first, followed by the document refs to get. A nice error message or a warning in the console (somehow) would suit better here than a TypeError
when we try to call our mocked DocumentReference
methods on the wrong type of object.
This sort of check would enforce internal consistency as well as user sanity. We would catch bugs caused by incorrect API calls before the user boots up a Firestore emulator or deploys their product!
Maintenance would be a problem. If Firestore's API suddenly changes to be more permissive with certain calls (unlikely in the case of variadic methods, but still a possibility) then we would need to update quickly to follow suit. This is why a logged warning may be in order. I don't know how to expose those properly to Jest other than regular console logs (which are invisible in --silent
runs), so we may need to think up some solution for that too.
We may instead create some way for users to assert the semantics of certain calls. For example, users can already assert that a Transaction
object was called upon to update a ref at a specific path since our mock ref gets passed directly to Transaction.update
. All one must do is expect
that mockTransactionUpdate
was called where the first argument includes the path
key and a given database path. My company's own projects have already adopted a custom matcher, toHaveFirestorePath
, for that very purpose, which may be useful enough to add here in a PR. If we can expand this sort of semantic assertion into more areas, such as direct modifications to Firestore refs (ref.update
, for example), which cannot be asserted in this same manner, then users of firestore-jest-mock would be able to replace the current idea of "This is how we interact with Firestore" with "This is how Firestore interprets my instructions." This way, any loss in semantic meaning when test Firestore interactions is caught and tested correctly, even more maintainably.
Is it possible to check if mockUpdate
was called on a document that is inside of a dataSnapshot.forEach
?
// function i am testing:
...
dataSnapshot.forEach((doc) => {
doc.update({param: 1})
})
...
But when i test using:
expect(mockCollection).toHaveBeenCalledWith('deals');
expect(mockUpdate).toHaveBeenCalled();
mockCollection
is passing, but mockUpdate
is not
I've been using firestore-jest-mock to mock all of my firestore functions for a project I'm currently working on and it's working great! I've run into one little snag, and that is the collectionGroup Firestore function has not been mocked yet.
Include Firestore collectionGroup function as a mocked function.
The collectionGroup function documentation can be found at the following link:
There should not be any change to the way mockFirebase is used. If I create a mockFirebase database as seen in the following example:
mockFirebase({
database: {
items: [
{id: '1', type: 'item', name: 'This is an item.', owner: 'doge', status: 'much wow'},
{id: '2', type: 'item', name: 'This is an item.', owner: 'doge', status: 'so scare'},
{id: '3', type: 'item', name: 'This is an item.', owner: 'doge', status: 'many science'},
{id: '3', type: 'other', name: 'This is not an item.', owner: 'cat', status: 'meow'}
]
}
});
mockFirebase should then mock function calls I am testing. In the following example I would expect the mocked functions to result in the function returning item objects 1, 2, and 3 from the above mockFirebase database example.
const db = firebase.firestore();
const query = db.collectionGroup('items').where('type', '==', 'item);
query.get().then( querySnapshot => {
return querySnapshot.docs;
}
I want to know how to add a current logged in user for the mock firebase. I have a function in a different file from my test file with the function:
export const getUserDocument = async (uid) => {
if (!uid) return null;
try {
const userDocument = await firestore.doc(`users/${uid}`).get();
return {
uid,
...userDocument.data(),
};
} catch (error) {
console.error("Error fetching user", error);
}
};
Then in a separate test file I put:
it("should return a user if the function is called with a uid provided", async () => {
//Promises need to be returned to properly throw errors
return getUserDocument("a1").then((uid, data) => {
//expect(mockCollection).toHaveBeenCalledWith('users');
expect(mockDoc).toHaveBeenCalledWith("users/a1");
expect(uid).toEqual("a1");
expect(data).not.toBeNull();
});
});
Expected behavior: the function returns the uid of "a1" and some data
Actual Behavior:
console.error
Error fetching user Error [FirebaseError]: Missing or insufficient permissions.
at new FirestoreError (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/util/error.ts:217:5)
at fromRpcStatus (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/serializer.ts:154:10)
at fromWatchChange (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/serializer.ts:476:33)
at PersistentListenStream.Object.<anonymous>.PersistentListenStream.onMessage (/Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/persistent_stream.ts:581:25)
at /Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/persistent_stream.ts:461:21
at /Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/remote/persistent_stream.ts:514:18
at /Users/anon/Documents/GitHub/PathwayWeb/node_modules/@firebase/firestore/src/util/async_queue_impl.ts:168:14
at processTicksAndRejections (internal/process/task_queues.js:93:5) {
code: 'permission-denied',
name: 'FirebaseError',
toString: [Function]
}
86 | };
87 | } catch (error) {
> 88 | console.error("Error fetching user", error);
| ^
89 | }
90 | };
It would seem I need to authenticate first which doesn't make sense because the mock library isn't suppose to have any installed rules, unless it's not using the mock library
QueryDocumentSnapshot that passed to trigger functions doesn't contain createTime element
Call createTime in snapshot passed into a trigger function.
The createTime should contain a value of the document created in the original scenario.
https://googleapis.dev/nodejs/firestore/latest/QueryDocumentSnapshot.html
createTime element is not available within the queryDocumentSnapshot that passed to trigger functions
For mocking purposes, we can provide the current time of the test suit or createdAt time if available on the FakeFirestore database.
In order to correct the issue, buildDocFromHash.js
file should return an object containing populated createTime.
For example:
module.exports = function buildDocFromHash(hash = {}, id = 'abc123') {
const exists = !!hash || false;
return {
exists,
id: (hash && hash.id) || id,
ref: hash && hash._ref,
createTime: hash.createdAt,
metadata: {
hasPendingWrites: 'Server',
},
...
Please correct me if I am wrong, but right now there is no support for querying on nested fields? For example, the code .where("info.startTime", ">=", serverTime)
does not work and will not match any documents. Doing the same check on a field that is in the root of the document works fine with simulateQueryFilters
set to true.
Is there a way to enable this, or perhaps have this a future feature?
Once again, thank you very much for all the hard work done here.
Basically what the title says. I have two tests and when i test them separately they have correct values, but when i run therm in bulk mockFirebase() with the values from the test above go to the one below despite the clearAllMocks method.
Describe the issue that you're seeing.
describe('getDetails', () => {
let spyCon = spyConsole();
beforeEach(() => {
jest.clearAllMocks();
});
it('returns formatted details', async () => {
mockFirebase({
database: {
collection: [
{
testingValOne: 'hello',
testingValTwo: 'world',
},
],
},
});
const firebase = require('firebase');
const db = firebase.firestore();
const result = await handleInfo.getDetails(db, mockAuth);
expect(mockWhere).toHaveBeenCalledWith('payerID', '==', '123');
expect(result).toEqual({
testingValOne: 'hello',
testingValTwo: 'world',
});
});
it('logs out a 404 error', async () => {
mockFirebase({
database: {
collection: [],
},
});
const firebase = require('firebase');
const db = firebase.firestore();
await handleInfo.getDetails(db, mockAuth);
expect(console.error).toHaveBeenCalledWith('404 resource not found');
});
});
The mock data from 'test returns true' gets passed to 'logs out a 404 error' where im trying to mock an empty collection.
Not sure if its the issue with jest, or this module to be honest, but this module being out of the norm i though ill ask here.
EDIT: managed to get it to get it to work with afterEach(()=> {jest.resetAllModules()}), though im not so sure about this solution if the tests potentially will have to share them.
EDIT2: Yeah, that was very shortlived. As soon as i started adding different tests and mocks that broke. The particularly temperamental ones are mockUpdate, mockDoc, mockCollection etc. Even clearing them directly with mockClear() doesnt work
EDIT3: ok, managed to get it to work after the last attempt and moving out the mockFirebase and db beyond the scope of describe fixed it with beforeEaach(()=>jest.clearAllMocks()) working.
Even though I did it differently than in the example provided (mocking db in each test),which should be an obvious clue. there was a good reason for it. The reason being that mocked where doesnt filter the response from the mocked db and some of the logic depended on the response from the db which naturally made me think that mocking db in each test with different data would solve the problem. Also, each test, when run separately, worked. Just when running them all together the mocked values werent getting reset.
Which leads me to another question. How to handle a situation where the logic is dependent on the response of the "where" query?
Clearly mocking firestore in each test doesnt work.
It looks like the mocks for firestore and auth (in tyhe firebase-admin) are outdated
Brief explanation of the feature.
When trying to mock firestore I got errors about recursiveDelete (in firestore) and getUserByEmail (in auth) not being functions. I added recursiveDelete on https://github.com/Upstatement/firestore-jest-mock/blob/master/mocks/firestore.js and getUserByEmail on https://github.com/Upstatement/firestore-jest-mock/blob/master/mocks/auth.js and my tests started passing
getUserByEmail() { return Promise.resolve(mockGetUser(...arguments) || {}); }
recursiveDelete() { return Promise.resolve(); }
If the proposal involves a new or changed API, include a basic code example. Omit this section if it's not applicable.
Why are we doing this? What use cases does it support? What is the expected outcome?
My workaround so far has being
`
import { FakeFirestore } from 'firestore-jest-mock/mocks/firestore';
Object.setPrototypeOf(FakeFirestore.prototype, {
...Object.getPrototypeOf(FakeFirestore.prototype),
recursiveDelete: jest.fn().mockResolvedValue('ok'),
});
import { FakeAuth } from 'firestore-jest-mock/mocks/auth';
Object.setPrototypeOf(FakeAuth.prototype, {
...Object.getPrototypeOf(FakeAuth.prototype),
getUserByEmail: jest
.fn()
.mockResolvedValue({ id: 'abc123', name: 'Homer Simpson' }),
});
`
The mocking system strips the id
from the mocked doc.data()
response. I would like it to keep id
in the resulting document.
mockFirebase( {
database: {
root: [
{
id: 'root-id',
_collections: {
subCollection: [
{
id: 'id-1',
test: true
},
],
},
},
],
},
} );
firestore.collectionGroup( 'subCollection' ) .where( 'id', '==', 'id-1' ).get()
.then((snap) => {
const resp = snap.docs[0].data();
// resp is {test: true} needs to be {id: 'id-1', test: true}
});
To allow us to search for a specific document within a collectionGroup
we keep the id
of our document stored as an id
field in the document. So we need to have the mock not strip that out when returning doc.data()
.
Not sure if it should be an option on mockFirebase
, automatic, or another field:
ex. {keepIds: true}
or {id: 'id-1', _id: 'id-1'}
,
It seems from my testing that each test file can only truly have 1 mockFirebase
as they override each other, is it true that if I want to test say, both getting a Full QuerySnapshot and an Enpty one, that I will need 2 test files? As you cannot set the database
field per test.
First of all, I want to appreciate the developers for such a great project.
Where query seems to return all documents, I think it should actually filter out the actual result instead of returning all result.
Create a sample test and query with the where query
It returns all results instead of the exact document that matches the where conditions.
For example, I have validation in my code to check if a user exists then I throw some error.
const existingUser = await db
.collection('users')
.where('email', '==', email.toLowerCase())
.get();
if (!existingUser.empty) {
throw new Error('User already exist');
}
I am unable to test this condition using this package since it returns all documents in the collection, irrespective of the query condition.
It should return only a document that matches the where query.
It returns all documents in the collections.
What happened.
The QueryDocumentSnapshot
passed to onCreate()
trigger failed to retrieve FakeFirestore
instance within its ref
value after query a document from the database.
onCreate()
functions trigger
export const setupActionTrigger = () => {
return functions.firestore.document('note/{id}').onCreate(async(snapshot) => {
if(snapshot.exists){
console.log(snapshot.ref);
const userRef = await admin.firestore().doc(`users/123`}).get();
console.log(snapshot.ref);
}
});
}
describe('actionTrigger', () => {
const test = firebaseFunctionsTest();
let actionTriggerFunction: WrappedFunction;
beforeAll(() => {
fakeFirestore = new FakeFirestore({
note: [{id: '123', note: 'test note', uid: 'xyz'}],
users: [{id:'xyz', username: 'xyzlmn'}]
});
mockFirebase({database: fakeFirestore.database});
admin.initializeApp();
const {setupActionTrigger} = require('../src/test-trigger');
actionTriggerFn = test.wrap(setupActionTrigger());
}
asterAll(() => {
jest.clearAllMocks();
jest.resetAllMocks();
test.cleanup();
});
it('should pass', async () => {
const doc = await fakeFirestore.doc('note/123').get();
await actionTrigger(doc);
})
}
snapshot.ref
print with FakeFirestore instance that provided by test suit.FakeFirestore
instance, instead it says firestore:undefined
Firesore instance with provided FakeFirestore instance would remain untouched with snapshot object after retrieving a document from the database query as shown in the trigger function
The snapshot
object reference FakeFirestore object went undefined after making the database query
You all did a great job with mocking two modules which are
KUDOS ๐ ๐
However, there is an assumption that the Auth module in the firebase module would be the same as the Auth module in firebase-admin but they are different i.e with different methods and properties
For more clarity:
This is the Auth module for Firebase Admin
This is the Auth module for Firebase
if you scroll to methods, you will see that the method list is different.
Try to use the auth
module from firebase-admin
, e.g
import * as admin from 'firebase-admin';
admin.initializeApp();
const auth = admin.auth();
auth.createUser({ email: '[email protected]', password: 'password', displayName: 'john doe' })
createUser
built-in as part of the FakeAuth
classoverrides
object TypeError: firebase_1.auth.createUser is not a function
Here is a link to a possible workaround solution
https://gist.github.com/johnkingzy/9097b5b65fc8a472ed51a35d7a0aba45
TLDR: I redefined the FakeAuth
class to match the module structure for firebase-admin
I'm getting this error when I try to query a sub collection from within a function:
const path = `mycol/${context.params.id}/mysubcol/${context.params.subcolId}/somethingelse`
const us = await db.collection(path).get()
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type object
at GrpcClient.loadProto (node_modules/@google-cloud/firestore/node_modules/google-gax/src/grpc.ts:166:23)
at new FirestoreClient (node_modules/@google-cloud/firestore/build/src/v1/firestore_client.js:113:32)
at ClientPool.Firestore._clientPool.pool_1.ClientPool [as clientFactory] (node_modules/@google-cloud/firestore/build/src/index.js:319:26)
at ClientPool.acquire (node_modules/@google-cloud/firestore/build/src/pool.js:81:35)
at ClientPool.run (node_modules/@google-cloud/firestore/build/src/pool.js:155:29)
at _retry (node_modules/@google-cloud/firestore/build/src/index.js:918:30)
at Firestore._retry (node_modules/@google-cloud/firestore/build/src/index.js:789:38)
NA
Query the mocked subcollection.
Error
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.