How to mock an imported Typescript class with Jest
Tags: jest, testing, angular, typescript
Published: 2020-04-02 23:52:31

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](https://jestjs.io/docs/en/es6-class-mocks#spying-on-the-constructor) 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](https://www.amadousall.com/how-to-set-up-angular-unit-testing-with-jest/) 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](https://github.com/kulshekhar/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](https://github.com/thymikee/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](https://github.com/kulshekhar/ts-jest/blob/master/docs/user/test-helpers.md) 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.