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?
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.
// 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')
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.
// 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')
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");
});
});
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')
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.