8

Lets say I have the following named export in a file customer.ts

export const saveDetails = ()=>{}
export const loadDetails = ()=>{}

assuming I'm using this in another file

import {saveDetails, loadDetails} from './customer.ts'

I want to mock mock './customer.ts' with a custom implementation. To that end I use the following code

const mockSaveDetails = jest.fn().mockImplementation(() => {});
jest.mock('./customer.ts', () => {
  return {
    saveDetails: mockSaveDetails
  };
});

Now when I run this code I get the following error

ReferenceError: Cannot access 'mockSaveDetails' before initialization

As per the documentation on https://jestjs.io/docs/en/es6-class-mocks I understand the mock is hoisted to the top and the exception is if the variable has the prefix mock. So as per the documentation this should work right ? If not what is the alternative to providing a mock mock implementation and spying on those implementation (like see howmany calls were made to saveDetails for example) with certain arguments.

Lin Du
  • 88,126
  • 95
  • 281
  • 483
tmp dev
  • 8,043
  • 16
  • 53
  • 108

4 Answers4

7

Here is the solution:

index.ts:

import { saveDetails, loadDetails } from "./customer";

export function main() {
  saveDetails();
  loadDetails();
}

customer.ts:

export const saveDetails = () => {
  console.log("real save details");
};
export const loadDetails = () => {
  console.log("real load details");
};

index.spec.ts:

import { main } from "./";
import { saveDetails, loadDetails } from "./customer";

jest.mock("./customer.ts", () => {
  return {
    saveDetails: jest.fn(),
    loadDetails: jest.fn()
  };
});

describe("main", () => {
  it("should mock correctly", () => {
    main();
    expect(saveDetails).toBeCalledTimes(1);
    expect(loadDetails).toBeCalledTimes(1);
  });
});

Unit test result with 100% coverage:

PASS  src/stackoverflow/59024742/index.spec.ts
  main
    ✓ should mock correctly (5ms)

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.626s, estimated 9s

Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59024742

Lin Du
  • 88,126
  • 95
  • 281
  • 483
3

ReferenceError: Cannot access 'mockSaveDetails' before initialization

The mock-setup must be the first thing in the file, even before the imports. Then the mockSaveDetails will be recognised.

It has to do with hoisting the mocks, as stated by the docs, but somehow for TypeScript this isn't done as documented, so we need to help it a little bit...

gfoidl
  • 890
  • 10
  • 8
1

This answer doesn't work if you want to provide an implementation for one of the mocked functions.

The only solution I've found is to change the import statement, and then use spys on this.

customer.ts

const saveDetails = () => {
  console.log("real save details");
};
const loadDetails = () => {
  console.log("real load details");
};

export { saveDetails, loadDetails }

index.ts

import { saveDetails, loadDetails } from "./customer";

export function main() {
  saveDetails();
  loadDetails();
}

index.spec.ts

import { main } from ".";
import * as customer from "./customer";

describe("main", () => {
  it("should mock correctly", () => {
    const detailsSpy = jest.spyOn(customer, 'saveDetails')
      .mockImplementation(() => {return {}});
    const loadSpy = jest.spyOn(customer, 'loadDetails')
      .mockImplementation(() => {return {}});

    main();
    expect(detailsSpy).toBeCalledTimes(1);
    expect(loadSpy).toBeCalledTimes(1);
  });
});

graham
  • 19
  • 2
0

If you both want to mock a named function and provide an implementation you don't ned the import * as syntax. You can do the following:

// import the named module
import { useWalkthroughAnimations } from '../hooks/useWalkthroughAnimations';

// mock the file and its named export
jest.mock('../hooks/useWalkthroughAnimations', () => ({
  useWalkthroughAnimations: jest.fn()
}));

// do whatever you need to do with your mocked function
useWalkthroughAnimations.mockReturnValue({ pageStyles, goToNextPage, page });
so001
  • 418
  • 4
  • 16