Search code examples
javascriptjestjsmockingjest-fetch-mock

How to mock a constant that is a dependency of the service under test?


I am testing a file called connect-key.js. It has a dependency called keyvault-emulator.

File #1:

// connect-key.js file
const { save, retrieve, init  } = require('./keyvault-emulator')
....
....
....
// SOME TESTS

File #2:

// The keyvault-emulator.js file
const { storageConnectionString } = require('../config')

Now, how can I mock the value of storageConnectionString from my test file connect-key.spec.js?

I envision something like this:

// The connect-key.spec.js file
const keyvault_emulator = require('../keyvault-emulator');
const spy = jest.spyOn(keyvault_emulator, 'storageConnectionString');
spy.mockReturnValue('');

And this is the config file:

// config.js file
module.exports = {
  storageConnectionString: process.env.STORAGE_CONNECTION_STRING || process.env.Storage,
  keyVaultName: process.env.KEY_VAULT
}

Is this the proper way to do this? What is the best way to accomplish this?


Solution

  • Mocking Internal Dependency

    Before mocking internal dependency, the question needs to clarify what's the point of it; because Jest's mocking ability is somewhat limited. Since it was not mentioned in the question, I've personally picked some best possible cases with examples.

    To make it easier to understand, let's say there is an imaginary function called testFunction(); it is a function that returns the storageConnectionString mentioned earlier.

    1. for only specific function in test scope, nothing else.

    // key.spec.js
    const keyvault_emulator = require('../keyvault-emulator');
    js.mock('../keyvault-emulator', () => ({
      // everything is original, except testFunction
      ...jest.requireActual('../keyvault-emulator'),
      // this supposed to return () => storageConnectionString but it's mocked here.
      testFunction: jest.fn(() => 'mocked')
    })
    
    // ✅ it works
    expect(keyvault_emulator.testFunction()).toEqual('mocked')
    // ❌ this fails!
    expect(keyvault_emulator.otherFunctionUsingStorageConnectionString())
      .toEqual('mocked')
    

    2. for everything inside the module

    Jest can only replace a function or module. It can not re-evaluate the codes. In this case, the dependency of config.js and keyvault-emulator.js has to be decoupled from the source to ease the testing process.

    2-1. hard decoupling from the source itself.

    // keyvault-emulator.js
    // KeyValue emulator has to be restructured to have constructor, or init function
    class KeyValueEmulator {
      constructor(config) {
        this.config = config;
      }
      testFunction() {
        // do something with this.config
        return this.config;
      }
    }
    
    // key.spec.js
    const mockedConfig = { storageConnectionConfig: 'mocked' }
    const keyValueEmulator = new KeyValueEmulator(mockedConfig);
    // ✅ it works
    expect(keyValueEmulator.testFunction()).toEqual('mocked')
    

    2-2. soft decoupling with jest's internal module mocking.

    working codes at Github

    // key.spec.js
    import config from "./config";
    jest.mock("./config", () => ({ default: { storageConnectionString: "mocked" } }));
    import { storageConnectionString, testFunction } from "./index";
    
    describe("config mocking", () => {
      it("value is mocked", () => {
        // ✅ it works
        expect(config.storageConnectionString).toEqual("mocked");
        expect(testFunction()).toEqual("mocked");
      });
    });
    

    3. mocking a single variable

    As already explained in case 2, it is the impossible case. Jest just cannot mock a single variable; it is probably closest to the code mentioned in the question, and this is why clarification is needed.

    // same as code in the question. the test code should be fixed to work,
    // but let's say it's working as you've intended.
    // key.spec.js
    const keyvault_emulator = require('../keyvault-emulator');
    const spy = jest.spyOn(keyvault_emulator, 'storageConnectionString');
    spy.mockReturnValue('mocked');
    // ✅ it may work...but
    expect(keyvault_emulator.storageConnectionString).toEqual('mocked')
    // ❌ this fails!
    expect(keyvault_emulator.testFunction()).toEqual('mocked')
    

    So, what's the best way?

    Is this the proper way to do this? What is the best way to accomplish this?

    Like many cases in the world of devs, it depends on the case. Personally I'd pick a hard-decoupling method mentioned in 2-1 for general usage but there are many cases where it doesn't fit perfectly. Pick the one that best fits to your case.