Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Import function from a Jest manual mock with Typescript

I'm creating a custom mock (of an ES6 class) with Jest in a Typescript project. The mock creates end exports a few mock.fn() so that they could be spied on in the test suite.

An example could be the official one from the Jest documentation (https://jestjs.io/docs/en/es6-class-mocks#manual-mock). There the SoundPlayer class has been mocked, as it is its only method playSoundFile. The method is mocked using a jest.fn(), which is exported to be used in tests.

// soundPlayer.ts
export default class SoundPlayer {
  foo: string = 'bar';

  playSoundFile(filename: string) {
    console.log(`Playing sound file ${filename}`);
  }
}
// __mocks__/soundPlayer.ts
export const mockPlaySoundFile = jest.fn();

const mock = jest.fn().mockImplementation(() => {
  return { playSoundFile: mockPlaySoundFile };
});

export default mock;
// __tests__/soundPlayer.ts
import SoundPlayer, { mockPlaySoundFile } from '../soundPlayer';

jest.mock('../soundPlayer');

beforeEach(() => {
  mockPlaySoundFile.mockClear();
});

it('is called with filename', () => {
  const filename = 'song.mp3';
  const soundPlayer = new SoundPlayer();
  soundPlayer.playSoundFile(filename);

  expect(mockPlaySoundFile).toBeCalledWith(filename);
});

The test works as expected, but TS notifies an error (which kind of makes sense to me) when trying to import the mocked mockPlaySoundFile function. That is because, obviously, mockPlaySoundFile doesn't exist in soundPlayer.ts. But because of jest.mock('../soundPlayer'); the mock is imported under the hood, therefore the export does exist.

Is there a way to inform TS to look at the mocks in cases like this?

like image 584
CosmaTrix Avatar asked Oct 31 '19 08:10

CosmaTrix


2 Answers

Update 29 Sept 2022:

The mocked function has been integrated into jest and is not available in ts-jest anymore.


Original answer:

The easiest way to fix this is to use ts-jest's mocked() helper. The helper will make sure you have access to the mock test methods. __tests__/soundPlayer.ts would then look as follows:

// __tests__/soundPlayer.ts
import { mocked } from "ts-jest/utils";
import SoundPlayer from '../soundPlayer';

jest.mock('../soundPlayer');

const soundPlayer = mocked(new SoundPlayer());

beforeEach(() => {
  soundPlayer.playSoundFile.mockClear();
});

it('is called with filename', () => {
  const filename = 'song.mp3';

  soundPlayer.playSoundFile(filename);

  expect(soundPlayer.playSoundFile).toBeCalledWith(filename);
});

If you really want to include mockPlaySoundFile you could do it by telling the Typescript compiler to suppresses the import error:

// @ts-ignore
import { mockPlaySoundFile } from '../soundPlayer';

Also, have a look at the example in my repo here: https://github.com/tbinna/ts-jest-mock-examples, in particular, your soundPlayer example: https://github.com/tbinna/ts-jest-mock-examples/tree/master/sound-player

like image 140
Tobi Avatar answered Oct 23 '22 00:10

Tobi


I have the same problem and I only have a workaround. My problem is that I manually mock fs from node.

So I have a manual mock for 'fs' which looks roughly like this:

const fs = jest.genMockFromModule("fs");

let mockFiles = {};

function __clearMocks(){
    mockFiles = {};
}

module.exports = fs;

So clearly when my test case imports fs it would not work:

import * as fs from 'fs';
fs.__clearMocks();

To get this working I create an extension of the type:

declare module 'fs' {
    export function __clearMocks(): void; 
}

So now I can modify my testcase like this:

import * as fs from 'fs';
import 'fsExtension';
fs.__clearMocks();

Hopefully this helps you!

like image 41
Highriser Avatar answered Oct 23 '22 01:10

Highriser