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.
Top comments (13)
Merci pour m'aider avec un test difficile d'Electron. Maintenant je peu faire un mock de l'object BrowserWindow et tester le classes de niveu plus bas. Il-y-a un example:
how do you call the
playSoundFile
method?What took me forever to realize is the jest.mock has to be done above the describe block.
additionally, instead of using this library, I now use
Update: I now use the following method: dev.to/bmitchinson/mocking-classes...
I try to mock a FeathersJS service class with your article example without any success.
I cannot figure out how to check class methods calls.
I ended up an another way with this:
This works but the implements looks wrong. I have to first mock the whole class with
jest.mock
then mock one of the method while storing it on a variable for test check.Is it the way to go? Will you do the same way?
Thanks!
mocked()
function from ts-jest saved my life! Thank you for the tip :)You're welcome!
Merci Beaucoup.
De rien!
I got an error saying X.mockClear is not a function (X is the class I want to mock). I can't find anything wrong with my setup. Could you help suggest what I can do to resolve it. Thanks
@devakone thanks a lot man! 🚀
Thanks. It looks pretty easy.
I was wondering when a mockedClass is re-used throughout the project, how this may look like. jest.mock() does not return anything, does it?
Anyone looking for the link to the ts-jest mocked document, it is this kulshekhar.github.io/ts-jest/docs/...
Github and Kulshekhar have been inverted to find the docs.
I also found value in understanding what the Mock Implementation is doing. In this link, jonathancreamer.com/testing-typesc..., explains what that is before mockImplementation was created. An assumption on creation regardless it explains that well which makes this article very fluent.