5
votes

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?

2
This thread has a lot of good examples on how to do this: stackoverflow.com/questions/48759035/… You could use the mocked helper from ts-jest: github.com/kulshekhar/ts-jest/blob/master/docs/user/…Tobi

2 Answers

3
votes

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

1
votes

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!