loading...
Cover image for How to mock an imported Typescript class with Jest
Code D' Ivoire

How to mock an imported Typescript class with Jest

devakone profile image Abou Kone ・3 min read

Sooner or later in your unit tests you will run into an issue where you need to import a class into your test and mock it, to keep up with good test hygiene. Jest offers a pretty good how to in their documentation on how to set it up for ES6 classes but if you try those instructions out of the box with Typescript, you will run into the type monster. This is a quick post to get it working for Typescript if you're using Jest. If you're an Angular developer and have not set up Jest yet, follow this great tutorial by Amadou Sall, the bonus is that you will also set up jest-preset-angular, which will help down the road.

SoundPlayer Class

Let's say this is your sound-player.ts file:


export class SoundPlayer {
  constructor() {
    this.foo = 'bar';
  }

  playSoundFile(fileName) {
    console.log('Playing sound file ' + fileName);
  }
}

Notice that this is not a default export. That's an important factor that if you follow the Jest documentation, their examples assumes that you're using default exports, which will matter later on in the mock.

SoundPlayer Mock

Now let's say you're writing a unit test for another class, let's say SoundPlayerConsumer and you want to mock SoundPlayer. If you don't have ts-jest installed, I highly recommend to add it to your Jest configuration now.

yarn add --dev ts-jest @types/jest

Like I mentioned earlier, if you're using jest-preset-angular, it already comes "bundled" with ts-jest.

With ts-jest in the bag, mocking a Typescript class with Jest is as easy as:

import { mocked } from 'ts-jest/utils';
import { SoundPlayer } from './sound-player';

jest.mock('./sound-player', () => {
  return {
    SoundPlayer: jest.fn().mockImplementation(() => {
      return {
        playSoundFile: () => {},
      };
    })
  };
});

describe('SoundPlayerConsumer', () => {
  const MockedSoundPlayer = mocked(SoundPlayer, true);

  beforeEach(() => {
   // Clears the record of calls to the mock constructor function and its methods
   MockedSoundPlayer.mockClear();

  });

  it('We can check if the consumer called the class constructor', () => {
    const soundPlayerConsumer = new SoundPlayerConsumer();
    expect(MockedSoundPlayer).toHaveBeenCalledTimes(1);
  });

}

It's pretty self explanatory but here are some clarification points:

  • Contrarily to the Jest documentation, since we're not using a default export, we have to reflect the namespace of the exported class module:
return {
    SoundPlayer: jest.fn().mockImplementation(() => {
      return {
        playSoundFile: () => {},
      };
    }

If this was a default module, we could have written it simply as:

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

If you're getting a “TypeError: ”X“.default is not a constructor.” when trying to run your tests, it's because you have not reflected the exported namespace properly.

  • The magic here happens because of the mocked method, which according to the documentation :

The mocked test helper provides typings on your mocked modules and even their deep methods, based on the typing of its source. It make use of the latest TypeScript features so you even have argument types completion in the IDE (as opposed to jest.MockInstance).

The first tell tale sign that your setup is not right would be getting an error of type error TS2339: Property 'mockClear' does not exist on type X X being the class you are trying to mock.

I hope this helps you write better unit tests.

Posted on by:

devakone profile

Abou Kone

@devakone

CTO @ AKIL, founder @ CodeDivoire

Code D' Ivoire

Communaute de developpeurs ivoiriens

Discussion

markdown guide