Have you ever run into this error while creating a variable before your ++code>jest.mock++/code>:
++pre>++code data-line-start="6" data-line-end="24">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
);
++/code>++/pre>
🔴 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:
++pre>++code data-line-start="31" data-line-end="39">import SoundPlayer from './sound-player';
const mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
});
++/code>++/pre>
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 👇
Let's take a simpler example:
++table border="1" cellpadding="4" style="width: 100%; border-color: #ff0201; border-style: solid; border-collapse: collapse; table-layout: fixed; height: 345px;">++tbody>++tr style="height: 33px;">++td style="width: 100%; background-color: #eeeeee; height: 33px;">Fails++/td>++/tr>++tr style="height: 312px;">++td style="width: 100%; height: 312px;">++pre>++code data-line-start="63" data-line-end="69">const useDispatchMock = () => jest.fn();
jest.mock('react-redux', () => ({
useDispatch: useDispatchMock, // ❌ BREAKS, useDispatchMock needed while hoisting.
}));
++/code>++/pre>Here, ++code>useDispatchMock++/code> will be needed when doing the actual mocking (before ++code data-line-start="63" data-line-end="69">useDispatchMock++/code> initialization).
Jest needs to return ++code>useDispatchMock++/code> when you ask for ++code>useDispatch++/code>.
➡ it needs to know what it is right now (mocking phase)
++/td>++/tr>++/tbody>++/table>
++table border="1" cellpadding="4" style="width: 100%; border-color: #00FF03; border-style: solid; border-collapse: collapse; table-layout: fixed;">++tbody>++tr>++td style="width: 100%; background-color: #eeeeee;">Works++/td>++/tr>++tr>++td style="width: 100%;">++pre>++code data-line-start="77" data-line-end="83">const dispatchMock = jest.fn();
jest.mock('react-redux', () => ({
useDispatch: () => dispatchMock, // ✅ WORKS, dispatchMock needed only on execution
}));
++/code>++/pre>Here, ++code>dispatchMock++/code> is not necessary when doing the actual mocking. It's only necessary when ++code data-line-start="77" data-line-end="83">useDispatch++/code> is called, after ++code data-line-start="77" data-line-end="83">dispatchMock ++/code>++code data-line-start="77" data-line-end="83">++/code>initialization.
Jest needs to return a ++code>function++/code> when you ask for ++code>useDispatch++/code>
➡ this ++code>function++/code> will later return ++code>dispatchMock++/code> when you actually run it.
➡ jest doesn't need to know what ++code>dispatchMock++/code> is as long as you haven't run the ++code>function++/code>
➡ the ++code>function++/code> is not run right now (mocking phase)
++/td>++/tr>++tr>++td style="width: 100%;">
In Jest example:
++pre>++code data-line-start="97" data-line-end="105">import SoundPlayer from './sound-player';
const mockPlaySoundFile = jest.fn();
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
});
});
++/code>++/pre>
++code>mockPlaySoundFile++/code> is not necessary when doing the actual mocking.
Jest needs to return a ++code>jest.fn()++/code> when you ask for './sound-player'.
➡ this ++code>jest.fn()++/code> will later return ++code>mockPlaySoundFile++/code> (here it's inside an object returned because of the mockImplementation) when you actually run it.
➡ jest doesn't need to know what ++code>mockPlaySoundFile++/code> is as long as you haven't run the './sound-player'.
➡ 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.
++/td>++/tr>++/tbody>++/table>
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.
++pre>++code data-line-start="123" data-line-end="132">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
}));
++/code>++/pre>
And if you cannot do that (like in my 1st example & in the above example that breaks ❌) you can opt for the following solution:
++pre>++code data-line-start="136" data-line-end="154">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
);++/code>++/pre>