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

Justin L Beall - May 28 '18 - - Dev Community

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player