jest ReferenceError: Cannot access '' before initialization

23,699

Solution 1

The accepted answer does not handle when you need to spy on the const declaration, as it is defined inside the module factory scope.

For me, the module factory needs to be above any import statement that eventually imports the thing you want to mock. Here is a code snippet using a nestjs with prisma library.

// app.e2e.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import mockPrismaClient from './utils/mockPrismaClient'; // you can assert, spy, etc. on this object in your test suites.

// must define this above the `AppModule` import, otherwise the ReferenceError is raised.
jest.mock('@prisma/client', () => {
  return {
    PrismaClient: jest.fn().mockImplementation(() => mockPrismaClient),
  };
});

import { AppModule } from './../src/app.module'; // somwhere here, the prisma is imported

describe('AppController (e2e)', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });
)};

Solution 2

None of the answers above solved my problem, so here's my solution:

var mockMyMethod: jest.Mock;

jest.mock('some-package', () => ({
  myMethod: mockMyMethod
}));

Something about using const before the imports feels weird to me. The thing is: jest.mock is hoisted. To be able to use a variable before it you need to use var, because it is hoisted as well. It doesn't work with let and const because they aren't.

Solution 3

To clarify what Jason Limantoro said, move the const above where the module is imported:

const mockMethod1 = jest.fn(); // Defined here before import.
const mockMethod2 = jest.fn();

import MyClass from './my_class'; // Imported here.
import * as anotherClass from './another_class';

jest.mock('./my_class', () => {
  return {
    default: {
      staticMethod: jest.fn().mockReturnValue(
        {
          method1: mockMethod1,
          method2: mockMethod2,
        })
    }
  }
});

Solution 4

The problem that the documentation addresses is that jest.mock is hoisted but const declaration is not. This results in factory function being evaluated at the time when mocked module is imported and a variable being in temporal dead zone.

If it's necessary to access nested mocked functions, they need to be exposed as a part of export object:

jest.mock('./my_class', () => {
  const mockMethod1 = jest.fn();
  const mockMethod2 = jest.fn();
  return {
    __esModule: true,
    mockMethod1,
    mockMethod2,
    default: {
      ...

This also applies to manual mocks in __mocks__ where variables are accessible inside a mock only.

Share:
23,699
Sufiane
Author by

Sufiane

Updated on July 27, 2021

Comments

  • Sufiane
    Sufiane almost 3 years

    I'm getting the error:

    ReferenceError: Cannot access 'myMock' before initialization

    Even though i respected jest documentation about the hoisting: A limitation with the factory parameter is that, 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'.

    I'm doing this:

    import MyClass from './my_class';
    import * as anotherClass from './another_class';
    
    const mockMethod1 = jest.fn();
    const mockMethod2 = jest.fn();
    jest.mock('./my_class', () => {
      return {
        default: {
          staticMethod: jest.fn().mockReturnValue(
            {
              method1: mockMethod1,
              method2: mockMethod2,
            })
        }
      }
    });
    

    as you can see both of my variables respect the "standard" but are not hoisted properly.

    Am I missing something ?

    Obviously it works when I just pass jest.fn() instead of my variables, but i'm not sure how to be able to use these in my test later on.

  • Sufiane
    Sufiane over 3 years
    mmmh yet the documentation state that there is an exception for variable starting with the word "mock". Thats strange ! I'll give your solution a try thanks !
  • Sufiane
    Sufiane over 3 years
    you sir are a savior ! it works like a charm !
  • Estus Flask
    Estus Flask over 3 years
    Glad it helped. There's no exception in a way variables are handled, it just requires a variable to contain mock to designate that a dev is fully aware of the consequences.
  • Sufiane
    Sufiane over 3 years
    ok, i guess i misunderstood the doc stating that there was an exception then
  • Sam
    Sam about 3 years
    @EstusFlask I'm definitely reading it the same way Sufiane is. Take a look at the example here: jestjs.io/docs/… They are doing almost the exact same thing the original asker is trying to do.
  • Estus Flask
    Estus Flask about 3 years
    @Sam They could do a better job at explaining the case they warn about. The obvious difference is that the documentation doesn't have anotherClass that does side effects on import.
  • Estus Flask
    Estus Flask almost 3 years
    Actually, this is not a correct way to address the problem. It's technically wrong to put anything above imports and expect that it will be evaluated in this order, because ESM imports are hoisted by specs, and jest.mock is hoisted by Jest via Babel transform, this is also specified. It may work in one setup and fail in another because the behaviour is undetermined. A correct way to use mockPrismaClient in a mock is to import it with require or jest.requireActual inside jest.mock instead of relying on a value from parent scope.
  • Jason
    Jason almost 3 years
    So far, this method has always worked for me for a variety of setups. However, I've never known if there is a "technically right way to do it". Mind sharing a piece of code example to what you just explained?
  • Estus Flask
    Estus Flask almost 3 years
    I meant basically it's import mpc from './utils/mockPrismaClient'; jest.mock('@prisma/client', () => { const mpc = require('./utils/mockPrismaClient').default; return { PrismaClient: jest.fn(() => mpc) } ));. So mock's dependency on utility module is explicitly specified and isn't tied to potential race conditions. Also this allows it to be extracted to __mocks__ if needed.
  • Reuel Ribeiro
    Reuel Ribeiro over 2 years
    I was trying using let outside of the jest.mock to assign it internally and it was failing. Using var solved my problem. Thanks.
  • danBhentschel
    danBhentschel over 2 years
    This is because: 1) Jest hoists jest.mock() calls. 2) Jest does not hoist variables that begin with mock. 3) Variables declared with var are always hoisted in JavaScript, whereas variables declared with let and const are not.