I have two modules written in JavaScript. In one module, I have a function that generates a random number, and in the other, a function that selects an element from an array based on this random number. An example is below:
randomNumber.js
export function generateRandomNumber() {
return a random number
}
selectElement.js
import { generateRandomNumber } from './randomNumber.js'
export function selectRandomElement() {
const myArray = ['a', 'b', 'c', 'd']
const randomNumber = generateRandomNumber()
return myArray[randomNumber]
}
The above is sudo-code. I've simplified the actual code.
I would like to use Jest to test the selectElement.js module. This module depends on the generateRandomNumber function in the other module. I want to mock that function so that every time I call selectRandomElement, I get the exact same result like so (again, sudo-code):
__tests__/selectElement.test.js
import { selectRandomElement } from '../selectElement.js'
const generateRandomNumber = () => 0 // use this as the mock
test('check we get the letter a', () => {
expect(selectRandomElement()).toBe('a') // returns 'a' (the first element from the array)
expect(selectRandomElement()).toBe('a') // returns 'a' (the first element from the array)
expect(selectRandomElement()).toBe('a') // returns 'a' (the first element from the array)
expect(selectRandomElement()).toBe('a') // returns 'a' (the first element from the array)
});
My intention is to produce consistent behavior in the tests. i.e. it always returns 'a' as when I call selectRandomElement in the tests I will always get the first element of the array as it's using the mocked function.
Is it possible to do this?
I would like to use ESM, and leave the structure of the modules as is. No refactoring. Only the test file can be changed.
I have work this out. My original mock code was a bit off. There are various things to change to make this work:
jest.mock(...
needs to be changed to jest.unstable_mockModule(...
. Mock was not in my original code, but it is probably in your if you are having this issue. See below if not for what to do.const { generateRandomNumber } = await import('../randomNumber.js')
. You must include await
before import()
.__esModule: true
defined in the mock."transform": {}
in your jest config file. See list item 1 here for details. This needs to be changed if you are using Typescript. This is not specific for mocking. I add it for completeness.You do not need to change any of the source code (ie the non-test code) to accommodate the changes.
Here is my test script above with all the updates needed.
__tests__/selectElement.test.js
import { selectRandomElement } from '../selectElement.js'
jest.unstable_mockModule('../randomNumber.js', () => ({
__esModule: true, // Point 4 in the list above
generateRandomNumber: jest.fn(() => 0)
}))
// Point 3 in the list above. jest.unstable_mockModule is defined first.
// The dynamic import is defined second.
const { generateRandomNumber } = await import('../randomNumber.js') // Point 2 in the list above
test('check we get the letter a', () => {
expect(selectRandomElement()).toBe('a') // returns 'a' (the first element from the array)
expect(selectRandomElement()).toBe('a') // returns 'a' (the first element from the array)
expect(selectRandomElement()).toBe('a') // returns 'a' (the first element from the array)
expect(selectRandomElement()).toBe('a') // returns 'a' (the first element from the array)
});
This has taken me a long time to work out. It is a bit ridiculous that this is not better documented in the Jest docs. ESM has been around and integrated into Nodejs for a very long time now.