Jest_Handy_References
Jacshuo Lv3

ES6 Class Mocks

sound-player.js

1
2
3
4
5
6
7
8
9
export default class SoundPlayer {
constructor() {
this.foo = 'bar';
}

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

sound-player-consumer.js

1
2
3
4
5
6
7
8
9
10
11
12
import SoundPlayer from './sound-player';

export default class SoundPlayerConsumer {
constructor() {
this.soundPlayer = new SoundPlayer();
}

playSomethingCool() {
const coolSoundFileName = 'song.mp3';
this.soundPlayer.playSoundFile(coolSoundFileName);
}
}

Mocking a specific method of a class

Let’s say that you want to mock or spy the method playSoundFile within the class SoundPlayer. A simple example:

1
2
3
4
5
6
7
8
9
10
11
12
// your jest test file below
import SoundPlayer from './sound-player';
import SoundPlayerConsumer from './sound-player-consumer';

const playSoundFileMock = jest.spyOn(SoundPlayer.prototype, 'playSoundFile').
mockImplementation(() => {console.log('mocked function');}); // comment this line if just want to "spy"

it('player consumer plays music', () => {
const player = new SoundPlayerConsumer();
player.playSomethingCool();
expect(playSoundFileMock).toHaveBeenCalled();
});

Static, getter and setter methods

Let’s imagine our class SoundPlayer has a getter method foo and a static method brand:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default class SoundPlayer {
constructor() {
this.foo = 'bar';
}

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

get foo() {
return 'bar';
}

static brand() {
return 'player-brand';
}
}

You can mock/spy them easily, here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// your jest test file below
import SoundPlayer from './sound-player';
import SoundPlayerConsumer from './sound-player-consumer';

const staticMethodMock = jest.spyOn(SoundPlayer, 'brand').mockImplementation(() => 'some-mocked-brand');

const getterMethodMock = jest.spyOn(SoundPlayer.prototype, 'foo', 'get').mockImplementation(() => 'some-mocked-result');

it('custom methods are called', () => {
const player = new SoundPlayer();
const foo = player.foo;
const brand = SoundPlayer.brand();

expect(staticMethodMock).toHaveBeenCalled();
expect(getterMethodMock).toHaveBeenCalled();
});

Keeping track of usage (spying on the mock)

Spying on the constructor

In order to track calls to the constructor, replace the funct ion returned by the HOF with a Jest mock function. Create
it with jest.fn(), and then specify its implementation with mockImplementation().

1
2
3
4
5
6
7
8
import SoundPlayer from './sound-player';

jest.mock('./sound-player', () => {
// Works and lets you check for constructor calls:
return jest.fn().mockImplementation(() => {
return {playSoundFile: () => {}};
});
});

This will let us inspect usage of our mocked class, using
SoundPlayer.mock.calls: expect(SoundPlayer).toHaveBeenCalled(); or
near-equivalent: expect(SoundPlayer.mock.calls.length).toEqual(1);

Mocking non-default class exports

If the class is not the default export from the module then you need to return an object with the key that is the same
as the class export name.

1
2
3
4
5
6
7
8
9
10
import { SoundPlayer } from './sound-player'; // SoundPlayer is not default exported.

jest.mock('./sound-player', () => {
// Works and lets you check for constructor calls:
return {
SoundPlayer: jest.fn().mockImplementation(() => {
return {playSoundFile: () => {}};
}),
};
});

Spying on methods of our class

Our mocked class will need to provide any member functions (playSoundFile in the example) that will be called during our
tests, or else we’ll get an error for calling a function that doesn’t exist. But we’ll probably want to also spy on
calls to those methods, to ensure that they were called with the expected parameters.

A new object will be created each time the mock constructor function is called during tests. To spy on method calls in
all of these objects, we populate playSoundFile with another mock function, and store a reference to that same mock
function in our test file, so it’s available during tests.

1
2
3
4
5
6
7
8
9
import SoundPlayer from './sound-player';

const mockPlaySoundFile = jest.fn(); // should starts with `mock`, see CAUTION
jest.mock('./sound-player', () => {
return jest.fn().mockImplementation(() => {
return {playSoundFile: mockPlaySoundFile};
// Now we can track calls to playSoundFile
});
});

CAUTION
Since calls to jest.mock() are hoisted to the top of the file, Jest prevents access to out-of-scope variables. By
default, you cannot first define a variable and then use it in the factory. Jest will disable this check for variables
that start with the word mock. However, it is still up to you to guarantee that they will be initialized on time. Be
aware of Temporal Dead Zone.

References

  • 本文标题:Jest_Handy_References
  • 本文作者:Jacshuo
  • 创建时间:2022-08-28 00:42:14
  • 本文链接:https://blog.imade.life/2022/08/28/Jest-Handy-References/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!