Home > Enterprise >  Jest: Mock an NPM module method
Jest: Mock an NPM module method

Time:10-27

I need to test a function where I call another function from an NPM package called 'heartbeats':

index.ts

export async function checkUp(app: App, heart, beats: number, iterations: number): Promise<void> {
  // const heart = heartbeats.createHeart(1000, 'checkUp');
  heart.createEvent(beats, { countTo: iterations }, async (count, last) => {
    const secondCheck = await secondCheckStatus();
    if (!secondCheck) {
      app.quit();
    }
  });
}

index.test.ts

import * as Heartbeats from 'heartbeats';
import { secondCheckStatus } from './utils';

...

jest.mock('./utils', () => ({
  ...jest.requireActual('./utils'),
  secondCheckStatus: jest.fn(),
}));

const mockSecondCheckStatus = secondCheckStatus as jest.MockedFunction< typeof secondCheckStatus >;

...

beforeEach(() => {
  jest.clearAllMocks();
});

...

it('should auto kill app after checks', async () => {
  const mockApp = new MockApp() as unknown as jest.Mocked<App>;
  const mockHeart = Heartbeats.heart as unknown as jest.Mock;

  const mockCreateEvent = Heartbeats.heart.createEvent as unknown as jest.MockedFunction<
    typeof Heartbeats.heart.createEvent
  >;
  mockCreateEvent.mockImplementation((beats, iter, cb) => {
    cb(null, null);
  });

  mockSecondCheckStatus.mockResolvedValueOnce(false);
  mockApp.requestSingleInstanceLock.mockReturnValue(true);
  const isRunning = await checkUp(mockApp, mockHeart, 1, 1);
  await main(mockApp);

  expect(mockApp.quit).toHaveBeenCalledTimes(1);
  expect(isRunning).toBe(false);
});

But I always get:

TypeError: Cannot read property 'mockImplementation' of undefined

  83 |     typeof Heartbeats.heart.createEvent
  84 |   >;
> 85 |   mockCreateEvent.mockImplementation((beats, iter, cb) => {

Any idea what I am doing wrong?

Thanks a lot (still long way for me to work smoothly with Jest)

CodePudding user response:

Since the checkUp function accepts app and heart as its parameters, you can create mock objects that match these parameter types or interfaces.

Only the secondCheckStatus function is imported by the import keyword, you have to use jest.mock() method to mock it.

For handling the TS type issue for mocked object/function, you can use mocked(item: T, deep = false) helper function of ts-jest.

E.g.

index.ts:

import { secondCheckStatus } from './utils';

export async function checkUp(app, heart, beats: number, iterations: number): Promise<void> {
  heart.createEvent(beats, { countTo: iterations }, async (count, last) => {
    const secondCheck = await secondCheckStatus();
    if (!secondCheck) {
      app.quit();
    }
  });
}

util.ts:

export async function secondCheckStatus() {
  return true;
}

index.test.ts:

import { checkUp } from './';
import { secondCheckStatus } from './utils';
import { mocked } from 'ts-jest/utils';

jest.mock('./utils', () => ({
  ...(jest.requireActual('./utils') as object),
  secondCheckStatus: jest.fn(),
}));

const mockSecondCheckStatus = mocked(secondCheckStatus);

describe('69720608', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });
  test('should pass', async () => {
    const mockApp = {
      quit: jest.fn(),
    };
    const mockHeart = {
      createEvent: jest.fn().mockImplementation(async (beats, options, callback) => {
        await callback();
      }),
    };
    await checkUp(mockApp, mockHeart, 1, 1);
    expect(mockSecondCheckStatus).toBeCalledTimes(1);
    expect(mockApp.quit).toBeCalledTimes(1);
  });
});

test result:

 PASS  examples/69720608/index.test.ts (13.424 s)
  69720608
    ✓ should pass (4 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   88.89 |       50 |      75 |    87.5 |                   
 index.ts |     100 |       50 |     100 |     100 | 6                 
 utils.ts |      50 |      100 |       0 |      50 | 2                 
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        15.478 s
  • Related