I have a series of functions, each performing various firestore interactions. How do I use Jest to mock these firestore calls? I would like to avoid using a library.
When I use jest.mock("firebase/app")
and jest.mock("firebase/firestore")
and other variations, I either get null TypeErrors, or errors indicating I am still referencing the actual import and not the mock: Error: ... make sure you call initializeApp()
.
For example, a simple function I want to test:
import firebase from "firebase/app"; import "firebase/firestore"; export const setDocData = (id, data) => { const newDoc = { created: firebase.firestore.FieldValue.serverTimestamp(), ...data }; firebase .firestore() .doc("docs/" + id) .set(newDoc); };
Notice how firebase is imported as usual, then firestore is imported a side effect. Also notice how firestore is called first as a function, then later referenced as a property. I believe this is the source of my trouble.
The trick is to create a chained API of mock functions, and set this on the firebase object, instead of importing and mocking firestore. The example below allows me to test the above example function, and also doc(). get() promises. Show activity on this post.
Discussing Jest SpyOn specifically, it can spy or mock a function on an object. It is useful when you want to watch (spy) on the function call and can execute the original implementation as per need. A mock will just replace the original implementation with the mocked one.
Here is the solution I have found. There isn't much information online about this, so I hope it helps someone.
EDIT: I believe you can do something similar using jests
/__MOCKS__/
folders system, rather than overwriting the firestore object as I do in this example.
The trick is to create a chained API of mock functions, and set this on the firebase object, instead of importing and mocking firestore. The example below allows me to test the above example function, and also doc().get()
promises.
const docData = { data: "MOCK_DATA" }; const docResult = { // simulate firestore get doc.data() function data: () => docData }; const get = jest.fn(() => Promise.resolve(docResult)); const set = jest.fn(); const doc = jest.fn(() => { return { set, get }; }); const firestore = () => { return { doc }; }; firestore.FieldValue = { serverTimestamp: () => { return "MOCK_TIME"; } }; export { firestore };
I declare it in a file that runs before all my tests do (see docs), and import and use it in my test files like this:
import firebase from "firebase/app"; import { firestore } from "../setupTests"; firebase.firestore = firestore; describe("setDocData", () => { const mockData = { fake: "data" }; beforeEach(() => { jest.clearAllMocks(); setDocData("fakeDocID", mockData); }); it("writes the correct doc", () => { expect(firestore().doc).toHaveBeenCalledWith("docs/fakeDocID"); }); it("adds a timestamp, and writes it to the doc", () => { expect(firestore().doc().set).toHaveBeenCalledWith({ created: "MOCK_TIME", fake: "data" }); }); });
It's been a while since any activity on this question, but still there's not much material online, here's my solution:
export default class FirestoreMock { constructor () { // mocked methods that return the class this.mockCollection = jest.fn(() => this) this.mockWhere = jest.fn(() => this) this.mockOrderBy = jest.fn(() => this) // methods that return promises this.mockAdd = jest.fn(() => Promise.resolve(this._mockAddReturn)) this.mockGet = jest.fn(() => Promise.resolve(this._mockGetReturn)) // methods that accepts callbacks this.mockOnSnaptshot = jest.fn((success, error) => success(this._mockOnSnaptshotSuccess)) // return values this._mockAddReturn = null this._mockGetReturn = null this._mockOnSnaptshotSuccess = null } collection (c) { return this.mockCollection(c) } where (...args) { return this.mockWhere(...args) } orderBy (...args) { return this.mockOrderBy(...args) } add (a) { return this.mockAdd(a) } get () { return this.mockGet() } onSnapshot (success, error) { return this.mockOnSnaptshot(success, error) } set mockAddReturn (val) { this._mockAddReturn = val } set mockGetReturn (val) { this._mockGetReturn = val } set mockOnSnaptshotSuccess (val) { this._mockOnSnaptshotSuccess = val } reset () { // reset all the mocked returns this._mockAddReturn = null this._mockGetReturn = null this._mockOnSnaptshotSuccess = null // reset all the mocked functions this.mockCollection.mockClear() this.mockWhere.mockClear() this.mockOrderBy.mockClear() this.mockAdd.mockClear() this.mockGet.mockClear() } }
And here's an example usage:
import FirestoreMock from '../test_helpers/firestore.mock' import firebase from 'firebase/app' import 'firebase/firestore' describe('The Agreement model', () => { const firestoreMock = new FirestoreMock() beforeEach(() => { firebase.firestore = firestoreMock firestoreMock.reset() }) it('does something', (done) => { firestoreMock.mockAddReturn = { id: 'test-id' } firebase.firestore.collection('foobar') .add({foo: 'bar'}) .then(res => { expect(firestoreMock.mockCollection).toBeCalledWith('foobar') expect(firestoreMock.mockAdd).toBeCalledWith({foo: 'bar'}) expect(res.id).toEqual('test-id') done() }) .catch(done) }) })
If there is any interest out there I'm fine with packaging the FirestoreMock
implementation so that it can be easily shared
Teo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With