Have you ever run into this error while creating a variable before your jest.mock
:
const mockIsLoggedIn = jest.fn();
jest.mock('./utils', () => ({
...
isLoggedIn: mockIsLoggedIn,
}));
it('does ... when user is logged in', () => {
mockIsLoggedIn.mockReturnValue(true);
... // test while logged in
);
it('does ... when user is logged out', () => {
mockIsLoggedIn.mockReturnValue(false);
... // test while logged out
);
🔴 Test suite failed to run: ReferenceError: Cannot access 'mockIsLoggedIn' before initialization
Maybe you don't even understand why because the official docs displays this working example:
import SoundPlayer from './sound-player';
const mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
});
They even mention:
"since calls to jest.mock() are hoisted to the top of the file, it's not possible to first define a variable and then use it in the factory. An exception is made for variables that start with the word 'mock'."
⚠️ But the very next sentence, "It's up to you to guarantee that they will be initialized on time!" is what's crucial here.
The in-depth explanation is here 👇
mock
.jest.mock
, the babel plugin checks variables that are being used in the mock factory to ensure that everything being used will be within scope after hoisting.mock
as a way to tell jest that you know what you're doing.
Let's take a simpler example:
Fails |
Here, useDispatchMock will be needed when doing the actual mocking (before useDispatchMock initialization).Jest needs to return ➡ it needs to know what it is right now (mocking phase) |
Works |
Here, dispatchMock is not necessary when doing the actual mocking. It's only necessary when useDispatch is called, after dispatchMock initialization.Jest needs to return a ➡ this ➡ jest doesn't need to know what ➡ the |
In Jest example:
Jest needs to return a ➡ this ➡ jest doesn't need to know what ➡ this is exactly like the above example but with a complex syntax ➡ maybe this article can help you get an even better grasp of what's happening under the hood. |
Do like in the jest docs when you can. ➡ This works when what you want control over isn't returned directly by the mock but rather by a function in the mock.
const myMock = jest.fn();
jest.mock('lib', () => ({
useLogin: () => myMock(), // ✅ WORKS, myMock needed only on execution
logout: myMock, // ❌ BREAKS, myMock needed while mocking
complexHook: () => ({ callback: myMock }), // ✅ WORKS, myMock needed only on execution
complexSyntaxHook: jest.fn().mockImplementation(() => ({ callback: myMock })), // ✅ WORKS, myMock needed only on execution
}));
And if you cannot do that (like in my 1st example & in the above example that breaks ❌) you can opt for the following solution:
import { isLoggedIn } from './utils';
jest.mock('./utils', () => ({
...
isLoggedIn: jest.fn(),
}));
it('does ... when user is logged in', () => {
(isLoggedIn as jest.Mock).mockReturnValue(true); // 'as jest.Mock' so that typescript understands you're not using the real 'isLoggedIn' but a mocked one.
... // test while logged in
);
it('does ... when user is logged out', () => {
(isLoggedIn as jest.Mock).mockReturnValue(false);
... // test while logged out
);