In this article, I’ll present a few useful techniques when you need to mock imported constants and change them per test.
Imagine having a file that exports an INTERVAL constant, which will be used in some other files.
// constants.js
export const INTERVAL = 500
Now, you may have some logic depending on that interval being set, or being of a certain value range, etc. It makes sense to include these conditions in the tests, so you want to mock the value of the INTERVAL constant, and change it depending on the test.
As it stands out, there are a few ways to do this, depending on your situation.
First: the WRONG way
// someTest.test.js
let mockInterval = 1000
jest.mock("./constants", () => ({
__esModule: true,
INTERVAL: mockInterval, // <- this is WRONG
}))
it("test 1", () => {
// mockInterval expected to be 1000
})
it("test 2", () => {
mockInterval = 200
// mockInterval expected to be 200
})
Doing this will make Jest throw an error:
ReferenceError: Cannot access 'mockInterval' before initialization
This happens because Jest mocks are being hoisted to the top of the file, so when the code is run, the order of execution will look more or less like this:
- Jest mocks the ./constants file:
jest.mock("./constants", () => ({
__esModule: true,
INTERVAL: mockInterval, // <-mockInterval not yet declared
}))
- Declaration of mockInterval
let mockInterval = 1000
Therefore Jest is right — you just can’t access the mockInterval variable before it has been declared.
Solution 1: Use getters
In order to achieve the desired behavior, you can use JavaScript getters. The trick is that at runtime, Jest will evaluate the mockInterval variable only when the INTERVAL const will be looked up, which doesn’t happen at the beginning but when a test is run.
// someTest.test.js
let mockInterval = 1000
jest.mock("./constants", () => ({
__esModule: true,
get INTERVAL() {
return mockInterval // <- this will work
},
}))
it("test 1", () => {
// mockInterval expected to be 1000
})
it("test 2", () => {
mockInterval = 200
// mockInterval expected to be 200
})
The mockInterval variable will be looked up only when the INTERVAL const is really evaluated when the test is running, and at that point in time, it will be already present in the execution context, and it will have the mocked value that you expect it to have.
Solution 2: Import the mocked file and change the constant directly in each test
This method is more straightforward, however it can have issues when you use TypeScript.
Here is a great article explaining this method in-depth.
// someTest.test.js
import * as constants from "./constants"
jest.mock("./constants", () => ({
__esModule: true,
INTERVAL: 1000,
}))
it("test 1", () => {
// mockInterval expected to be 1000
})
it("test 2", () => {
// directly modify the constant
constants.INTERVAL = 200
// mockInterval expected to be 200
})
When you use jest.mock() on some module, every time that module will be imported, Jest will replace the original one with your implementation. Thanks to this, you can just override that implementation as shown above.
Solution 2: Fixing TypeScript issues
Sometimes, when using this solution paired with TypeScript, when you try to re-assign the constant like this:
constants.INTERVAL = 200
You will get the error:
Cannot assign to 'INTERVAL' because it's a read-only property
In order to fix it, you can do:
import * as constants from "./constants";
const mockConstants = constants as { INTERVAL: number };
// and then in a test
mockConstants.INTERVAL = 200;
Changing mocked constant variables per test is not something you probably need to do daily, but in the rare occasion when it is needed, this neat tricks can come in handy.