61

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
  • 1,507
  • 1
  • 14
  • 23

11 Answers11

54

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.

Mauro Vinicius
  • 761
  • 7
  • 13
  • 4
    I was trying using `let` outside of the `jest.mock` to assign it internally and it was failing. Using `var` solved my problem. Thanks. – Reuel Ribeiro Nov 10 '21 at 00:51
  • 10
    This is because: 1) [Jest hoists](https://jestjs.io/docs/manual-mocks#using-with-es-module-imports) `jest.mock()` calls. 2) [Jest does not hoist](https://github.com/kulshekhar/ts-jest/issues/1088#issuecomment-562975615) variables that begin with `mock`. 3) Variables declared with `var` [are always hoisted](https://stackoverflow.com/a/11444416) in JavaScript, whereas variables declared with `let` and `const` are not. – danBhentschel Jan 12 '22 at 15:46
  • 1
    I tried it but failed to work with error "TypeError: mockMyMethod is not a function" – Orionpax Dec 02 '22 at 09:42
  • 1
    @Orionpax you should be able to use `mockMyMethod` if you define it as a function! – Mauro Vinicius Dec 05 '22 at 11:35
29

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();
  });
)};
Jason
  • 314
  • 4
  • 5
  • 7
    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. – Estus Flask Jun 10 '21 at 12:44
  • 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? – Jason Jun 12 '21 at 01:18
  • 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. – Estus Flask Jun 12 '21 at 05:45
22

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,
        })
    }
  }
});
Matt
  • 4,261
  • 4
  • 39
  • 60
11

You should move your mocking above your imports; that could be the source of your issue. Imports are also hoisted, so multiple hoisted entries would be hoisted in order.

jest.mock('./my_class', () => {
  const mockMethod = jest.fn() 
  const default = { staticMethod: jest.fn().mockReturnValue({ method: mockMethod }) };
  return { default, mockMethod };
});

import MyClass, { mockMethod } from './my_class';  // will import your mock
import * as anotherClass from './another_class';

However, if you for some reason can't do that, you could use doMock to avoid hoisting behaviour. If this happens on the top of your file, it should be a 1 to 1 change.

const mockMyMethod = jest.fn();
jest.doMock('some-package', () => ({ myMethod: mockMyMethod }));
Ricardo Nolde
  • 33,390
  • 4
  • 36
  • 40
10

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.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 1
    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 Jan 04 '21 at 08:09
  • 3
    you sir are a savior ! it works like a charm ! – Sufiane Jan 04 '21 at 08:21
  • 1
    ok, i guess i misunderstood the doc stating that there was an exception then – Sufiane Jan 04 '21 at 08:48
  • 1
    @EstusFlask I'm definitely reading it the same way Sufiane is. Take a look at the example here: https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter They are doing almost the exact same thing the original asker is trying to do. – Sam Mar 24 '21 at 20:00
  • 2
    @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 Mar 25 '21 at 05:14
2

I got in the same problem.

ReferenceError: Cannot access 'mockYourFunction' before initialization

The selected answer works, but the explanation is wrong, it is not because the import order. The reason it works is because the answer changed mockResturnValue to mockImplementation.

mockResturnValue will try to resolve the value immediately while mockImplementation will do it only when been invoked, given the time needed to your variable be there when needed.

some, to exemplify a working solution, without reorder imports or add var and avoiding some fervent comments in your PR, here is the important part of the code:

const mockPrismaClient = jest.fn();
jest.mock('@prisma/client', () => {
  return {
    PrismaClient: jest.fn().mockImplementation(() => mockPrismaClient),
  };
});
ViniciusCR
  • 83
  • 1
  • 7
1

Per this article... Anyone making it to this page may just be able to set the mocked function to a function returning a function call. As per the OP's code:

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(), // <- function returning function call
          method2: () => mockMethod2(),
        })
    }
  }
});
daleyjem
  • 2,335
  • 1
  • 23
  • 34
1

None of these answers helped. Trying to use var kept giving me the TypeError: mockMyMethod is not a function. Yes I tried several function styles (delegate, function, etc).

What worked for me (disclaimer, this might not work with 3rd party packages, it might only work with your custom packages) :

import * as somePackage from 'some-package';
...

describe('desc', () => {
  ...
  beforeEach(() => {
    var mockMyMethod: jest.fn().mockResolvedValue({myMethod: whateverHere, etc: null});
    jest.spyOn(somePackage, 'methodOrHookName').mockImplementation(mockMyMethod);
  });
  
  it(...)
  
  it('testWithDifferentValue', () => {
    var mockMyMethod: jest.fn().mockResolvedValue({myMethod: superDifferent, etc: 'etc'});
    jest.spyOn(somePackage, 'methodOrHookName').mockImplementation(mockMyMethod);    
    // write test that is different to other tests.
    
  });
}));
goamn
  • 1,939
  • 2
  • 23
  • 39
  • I forgot to mention, it turns out the issue was from a typing error I had, so check your types :) – goamn Aug 11 '23 at 11:29
0

This solution works for me and it's pretty easy for vuejs+ jest.

Two points to note:

  • you should declare the absolute path and not '@/js/network/repositories'
  • the getter helps to defer the instantiation
    const mockGetNextStatuses = jest.fn();
    const mockUpdatePrintingStatus = jest.fn();
    
    jest.mock('../../../../../../src/js/network/repositories/index.js', () => {
        return {
            get printing() {
                return {
                    getNextStatuses: mockGetNextStatuses,
                    updatePrintingStatus: mockUpdatePrintingStatus,
                }
            }
        }
    });

or

jest.mock('../../../../../../src/js/network/repositories/index.js', () => ({
    printing: {
        getNextStatuses: jest.fn(),
        updatePrintingStatus: jest.fn()
    }
}));
import { printing } from '../../../../../../src/js/network/repositories/index.js';


// and mock the module
printing.getNextStatuses.mockReturnValue(['XX','YY']);
0

This solution worked for me:

jest.mock("../user/useCases/GetUserBalancesUseCase.js", () => {
  return {
    GetUserBalancesUseCase: {
      create: jest.fn().mockReturnValue({
        execute: jest
          .fn()
          .mockReturnValue(require("./fixtures/user/useCaseResponse.js").useCaseResponse),
      }),
    },
  }
})

I couldn't use the fixture importing it using ESM syntax in this implementation. So I tried using require and fixed the import reference error issue.

This kind of mocking though, is not reliable because bypasses some logic that I want to have covered by the unit test. In my case is necessary to avoid interacting with the infrastructure.

As a reference, I need to test the output of an useCase given an input:

test("Get User Balances", async () => {
    const useCaseResponse = await getUserBalancesUseCase.execute({ account, USDPrices })
    expect(useCaseResponse).toEqual(getUserBalancesUseCaseResponse)
  })
d4vecarter
  • 19
  • 4
-2

Example of using TypeScript with Jest and mockDebug.js module

jest.mock('debug', () => {
  global.mockDebug = jest.fn();
  return () => global.mockDebug;
});

// usage
describe('xxx', () => {
    test('xxx', () => {
        expect(global.mockDebug.mock.calls.toString()).toContain('ccc');
    })
});
node_modules
  • 4,790
  • 6
  • 21
  • 37
skypesky
  • 1
  • 1