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!