I am initializing a firebase auth provider within a react application.
Given
// base.js
L01 import firebase from 'firebase';
L02 const config = {
L03 apiKey: process.env.REACT_APP_FIREBASE_KEY,
L04 authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
L05 databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
L06 projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
L07 storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
L08 messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID
L09 };
L10 if (!firebase.apps.length) {
L11 firebase.initializeApp(config);
L12 }
L13 const auth = firebase.auth();
L14 export {auth}
Task
Add unit tests to fully cover and assert the expected behavior for each line of base.js
.
As a kata
today, I wanted to hit each line of code within an imported JavaScript file. Mocking system wide imports, such as initializing to a database or api, is fundamental in mastering the units of a system.
As mentioned in my previous JavaScript unit test article, Languages allow imports to execute non-encapsulated code procedurally. Side effects within these files alter the state of the running system, such as connecting to a database or api, when the instruction pointer links to the file. As many units as possible should be able to exist within the system without dependency.
Test
// base.test.js
// harness
describe("Base", () => {
afterEach(() => {
jest.resetModules()
});
// tests go here
});
Test #1: Initialize App is not called when Firebase has Apps
- Assert that firebase does not call L11
firebase.initializeApp(config);
when it already has any existing apps. - Mock the value of firebase.apps to return a truthy value on L10,
firebase.apps = [1]
. - Use a spy, to assert that L13 was called only once.
- Use the spy, to assert the return value of its function is the default exported value from
base.js
.
test("firebase initializeApp not called if already initialized", () => {
const firebase = require('firebase');
jest.mock('firebase');
firebase.initializeApp = (config) => {
throw "Should not be hit in test"
};
firebase.apps = [1];
let mockAuth = jest.fn();
let authReturnValue = 'auth'
mockAuth.mockReturnValueOnce(authReturnValue)
firebase.auth = mockAuth;
let auth = require("./base");
expect(mockAuth.mock.calls.length).toBe(1);
expect(auth).toEqual({"auth": authReturnValue})
});
With this test, we execute each line of code, outside of L13.
Test #2: Initialize App is called with Firebase config variables
- Assert that initializeApp is called with the expected environment variables.
test("firebase initializeApp called with firebase environment variables", () => {
const firebase = require('firebase');
jest.mock('firebase');
// hold on to existing env
const env = process.env;
// set mock env variables
process.env = {
REACT_APP_FIREBASE_KEY: 'key',
REACT_APP_FIREBASE_DOMAIN: 'domain',
REACT_APP_FIREBASE_DATABASE: 'database',
REACT_APP_FIREBASE_PROJECT_ID: 'project',
REACT_APP_FIREBASE_STORAGE_BUCKET: 'bucket',
REACT_APP_FIREBASE_SENDER_ID: 'sender',
REACT_APP_EXTRA_KEY: 'extra'
};
const expectedConfig = {
apiKey: 'key',
authDomain: 'domain',
databaseURL: 'database',
projectId: 'project',
storageBucket: 'bucket',
messagingSenderId: 'sender'
};
// spy for initializeApp
let mockInitializeApp = jest.fn();
firebase.initializeApp = mockInitializeApp;
firebase.apps = [];
require("./base");
expect(mockInitializeApp.mock.calls[0][0]).toEqual(expectedConfig);
// restore env
process.env = env;
});
Conclusion
Jest continues to surprise me. I found it's Mock Functions Documentation to be very user friendly! Mocking is always a tricky subject when it comes to unit testing. Be sure to ask questions if you don't get what is going on here!
Top comments (1)
Thank you for this awesome example of using Jest to mock Firebase.
in the Test #2 since you've mocked a module with jest.mock('firebase')
i dont think that we need to call those two lines:
let mockInitializeApp = jest.fn();
firebase.initializeApp = mockInitializeApp;
since
jest.mock
will automatically set all exports of a module to the Mock Function