Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you mock Firebase Firestore methods using Jest?

Tags:

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.

like image 986
Brendan McGill Avatar asked Aug 27 '18 17:08

Brendan McGill


People also ask

How do you mock a firestore?

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.

What is jest SpyOn?

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.


2 Answers

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"     });   }); }); 
like image 128
Brendan McGill Avatar answered Sep 19 '22 17:09

Brendan McGill


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

like image 20
teone Avatar answered Sep 19 '22 17:09

teone