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.
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
I found that mocking the import works well. I added this code in the test, above where I render my component importing 'firebase/app'
jest.mock('firebase/app', () => ({
__esModule: true,
default: {
apps: [],
initializeApp: () => {},
auth: () => {},
},
}));
If mocking seems tedious, don't. Use the emulators.
I believe this is a relatively new option for handling reads & writes in testing, so I'm posting it. Here's a quick walk-through.
$ curl -sL firebase.tools | bash
$ firebase init
const db = firebase.initializeApp(config).firestore()
if (location.hostname === "localhost") {
db.settings({
host: "localhost:8080",
ssl: false
});
}
$ firebase emulators:start
describe('New city', () => {
it('should create a new city in firestore', async () => {
await db.collection('cities').doc('Seattle').set({ state: "WA" })
const city = await db.collection('cities').doc("Seattle").get()
expect(city.data()['population']).toEqual("WA")
})
})
async function cleanFirestore() {
const Http = new XMLHttpRequest();
const url = "http://localhost:8080/emulator/v1/projects/<YOUR-PROJECT-ID>/databases/(default)/documents"
Http.open("DELETE", url);
Http.send();
return new Promise((resolve, reject) => {
setTimeout(reject, 2000)
Http.onreadystatechange = resolve
})
}
For an emulator walkthrough guide from Google: https://google.dev/pathways/firebase-emulators
Docs: https://firebase.google.com/docs/emulator-suite
I used the dependancy injection approach on components and it meant I could mock and test methods without all the boilerplate.
For example, I have a form component that handles invites like so:
import React, { useEffect } from 'react';
import { Formik } from 'formik';
import { validations } from '../../helpers';
import { checkIfTeamExists } from '../helpers';
const Invite = ({ send, userEmail, handleTeamCreation, auth, db, dbWhere }) => {
useEffect(() => {
checkIfTeamExists(send, dbWhere);
}, []);
return (
<Formik
initialValues={{ email: '' }}
onSubmit={values =>
handleTeamCreation(userEmail, values.email, db, auth, send)
}
validate={validations}
render={props => (
<form onSubmit={props.handleSubmit} data-testid="form">
<input
type="email"
placeholder="Please enter your email."
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.email}
name="email"
/>
{props.errors.email && (
<p className="red" data-testid="error">
{props.errors.email}
</p>
)}
<button type="submit">Submit</button>
</form>
)}
/>
);
};
export default Invite;
The checkIfTeamExists
method relies on firebase auth and handleTeamCreation
method writes to the firestore.
When I referenced the component in its parent I instantiated it like so:
<Invite
send={send}
userEmail={value.user.user.email}
handleTeamCreation={handleTeamCreation}
auth={auth.sendSignInLinkToEmail}
db={db.collection('games')}
dbWhere={db.collection('games')
.where('player1', '==', value.user.user.email)
.get}
/>
Then, using react-testing-library
, in my tests I was able to mock things out with a simple jest.fn()
.
test('Invite form fires the send function on Submit ', async () => {
const handleTeamCreation = jest.fn();
const send = jest.fn();
const userEmail = 'ex@mple.com';
const db = jest.fn();
const auth = jest.fn();
const dbWhere = jest.fn().mockResolvedValue([]);
const { getByPlaceholderText, getByTestId } = render(
<Invite
send={send}
userEmail={userEmail}
handleTeamCreation={handleTeamCreation}
auth={auth}
db={db}
dbWhere={dbWhere}
/>
);
const inputNode = getByPlaceholderText('Please enter your email.');
const email = 'me@gmail.com';
fireEvent.change(inputNode, { target: { value: email } });
const formNode = getByTestId('form');
fireEvent.submit(formNode);
await wait(() => {
expect(handleTeamCreation).toHaveBeenCalledWith(
userEmail,
email,
db,
auth,
send
);
expect(handleTeamCreation).toHaveBeenCalledTimes(1);
});
});
and mocked the firestore where query in the same way.
test('Invite form must contain a valid email address', async () => {
const send = jest.fn();
const db = jest.fn();
const dbWhere = jest.fn().mockResolvedValue([]);
const { getByPlaceholderText, queryByTestId } = render(
<Invite send={send} db={db} dbWhere={dbWhere} />
);
expect(queryByTestId('error')).not.toBeInTheDocument();
const inputNode = getByPlaceholderText('Please enter your email.');
const email = 'x';
fireEvent.change(inputNode, { target: { value: email } });
await wait(() => {
expect(queryByTestId('error')).toHaveTextContent('Invalid email address');
});
});
This is very simple, but it works. It's also quite verbose but I thought a real use case would be more helpful than a contrived example. I hope this helps someone.
I am using firebase.firestore.FieldValue.serverTimestamp()
in one of my functions:
import firestore from '@react-native-firebase/firestore';
import firebase from '@react-native-firebase/app';
export function getUserCreateObject() {
return {
property1: {
property2: {
value1: true,
last_updated_date: firebase.firestore.FieldValue.serverTimestamp(),
},
//the rest of the JSON object
},
};
}
To mock this, I have a jest.setup.js file which I reference in my package.json:
"jest": {
"preset": "react-native",
"moduleDirectories": [
"node_modules",
"src"
],
"transform": {
"\\.js$": "<rootDir>/node_modules/babel-jest"
},
"transformIgnorePatterns": [
"/node_modules/(?!(jest-)?react-native|@react-native-firebase/auth|@react-native-firebase/app|@react-native-firebase/app-types)"
],
"setupFiles": [
"./jest/jest.setup.js"
],
"coveragePathIgnorePatterns": [
"/node_modules/",
"/jest"
]
}
And in jest.setup.js I do:
jest.mock('@react-native-firebase/app', () => ({
firestore: {
FieldValue: {
serverTimestamp: jest.fn(),
}
}
}));