BAM

Fix jest mock 'Cannot access before initialization' error

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

  • JEST hoist the jest.mock block (? move it to the top of the file before running it).
  • JEST doesn't hoist variables that start with ++code>mock++/code>.
  • What happens is that when doing the hoisting for ++code>jest.mock++/code>, 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.
  • So if it finds a variable (if "mockPlaySoundFile" didn't start with "mock") it'll throw; it's effectively doing a little bit of what TypeScript does in general, to be helpful given that this hoisting is happening in the background & so not obvious. (warning ? this only happens if you use babel).
  • However, as an advanced escape hatch, the babel plugin allows variables that start with ++code>mock++/code> as a way to tell jest that you know what you're doing.

 

Why does my code throw and not the one in the docs?

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>

 

The general solution

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>

Développeur Mobile ?

Rejoins nos équipes