DEV Community

Justin L Beall
Justin L Beall

Posted on

I wrote a JavaScript Unit Test today... Spy On An Import Time Dependency

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}
Enter fullscreen mode Exit fullscreen mode

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
});

Enter fullscreen mode Exit fullscreen mode

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})
});
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

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!

Full source

unit test coverage


agile2018endorsement

Top comments (1)

Collapse
 
tahaazzabi profile image
taha azzabi • Edited

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