Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jest.mock with named exports how to spy

Tags:

mocking

jestjs

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.

like image 232
tmp dev Avatar asked Nov 25 '19 03:11

tmp dev


People also ask

How do you mock an exported const in jest?

To mock an exported const in Jest, we can use the jest. mock method. For instance, we write: const mockTrue = { ENABLED: true }; const mockFalse = { ENABLED: false }; describe('allowThrough', () => { beforeEach(() => { jest.

How do you mock an imported function jest?

To mock an imported function with Jest we use the jest. mock() function. jest. mock() is called with one required argument - the import path of the module we're mocking.

What is the purpose of jest spyOn?

Jest has a spyOn function, which can resolve the problem and make the test much better. jest. spyOn allows a method in an object to be mocked.


3 Answers

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

like image 65
slideshowp2 Avatar answered Oct 28 '22 01:10

slideshowp2


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...

like image 39
gfoidl Avatar answered Oct 28 '22 01:10

gfoidl


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);
  });
});

like image 39
graham Avatar answered Oct 28 '22 02:10

graham