To create unit tests, we often have to mock functions to bypass various parts of the code. For example, if we don’t want to run code that calls an API in a function, then we have to create a mock of that function.
In this article, we’ll look at some ways to create mocks functions and classes in Jest tests.
ES6 Class Mocks
We can use Jest to create ES6 class mocks.
ES6 classes are just syntactic sugar on top of constructor functions, so we can use the same methods for mock functions to mock classes.
Making Something to Test
First, we make something to test by creating videoPlayer.js
and add:
class VideoPlayer {
constructor() {
}
playVideoFile(fileName) {
console.log(`Playing ${fileName}`);
}
}
module.exports = VideoPlayer;
Then we create app.js
as follows:
const VideoPlayer = require('./videoPlayer');
class VideoPlayerApp {
constructor() {
this.videoPlayer = new VideoPlayer();
}
play() {
const videoFileName = 'video.mp4';
this.videoPlayer.playVideoFile(videoFileName);
}
}
module.exports = VideoPlayerApp;
Creating Automatic Mocks
In this example, we’ll mock the VideoPlayer
class.
To do this, we can call jest.mock(‘./videoPlayer’);
since we export the VideoPlayer
class with module.exports
.
Jest is smart enough to create its own mock class in place of the actual VideoPlayer
class in tests.
It’ll replace the class with a mock constructor function, and replaces all its methods with mock functions that always return undefined
.
Method calls are saved in theAutomaticMock.mock.instances[index].methodName.mock.calls
, where theAutomaticMock
is replaced with the same name as the class we originally mocked.
In our example, we aren’t going to create our own implementation of the methods, so once we called jest.mock(‘./videoPlayer’);
, we’re done with mocking the VideoPlayer
class.
Then we can create app.test.js
to put our tests. We add the following:
const VideoPlayer = require('./videoPlayer');
const VideoPlayerApp = require('./app');
jest.mock('./videoPlayer');
beforeEach(() => {
VideoPlayer.mockClear();
});
test('VideoPlayer is called once', () => {
const videoPlayerApp = new VideoPlayerApp();
expect(VideoPlayer).toHaveBeenCalledTimes(1);
});
test('VideoPlayer is called with video.mp4', () => {
expect(VideoPlayer).not.toHaveBeenCalled();
const videoPlayerApp = new VideoPlayerApp();
videoPlayerApp.play();
const videoFileName = 'video.mp4';
const mockVideoPlayer = VideoPlayer.mock.instances[0];
const mockPlayVideoFile = mockVideoPlayer.playVideoFile;
expect(mockPlayVideoFile.mock.calls[0][0]).toEqual(videoFileName);
expect(mockPlayVideoFile).toHaveBeenCalledWith(videoFileName);
expect(mockPlayVideoFile).toHaveBeenCalledTimes(1);
});
We important the modules for containing each class. The VideoPlayer
class will be replaced with our mock, but we still have to import it.
Then we have our beforeEach
hook which runs:
VideoPlayer.mockClear();
to clear the mocks of the VideoPlayer
class.
Next, we write our first test, which is:
test('VideoPlayer is called once', () => {
const videoPlayerApp = new VideoPlayerApp();
expect(VideoPlayer).toHaveBeenCalledTimes(1);
}););
We create a new VideoPlayerApp
instance. The constructor of the VideoPlayerApp
calls runs new VideoPlayer();
, which means the VideoPlayer
class is called.
Then we check that:
expect(VideoPlayer).toHaveBeenCalledTimes(1);
which should be true since the VideoPlayer
constructor function, aka the class, is called once.
Next, we look at our second test, which is more complex:
test('VideoPlayer is called with video.mp4', () => {
expect(VideoPlayer).not.toHaveBeenCalled();
const videoPlayerApp = new VideoPlayerApp();
videoPlayerApp.play();
const videoFileName = 'video.mp4';
const mockVideoPlayer = VideoPlayer.mock.instances[0];
const mockPlayVideoFile = mockVideoPlayer.playVideoFile;
expect(mockPlayVideoFile.mock.calls[0][0]).toEqual(videoFileName);
expect(mockPlayVideoFile).toHaveBeenCalledWith(videoFileName);
expect(mockPlayVideoFile).toHaveBeenCalledTimes(1);
});
First, we check that VideoPlayer
hasn’t been called to make sure that VideoPlayer.mockClear()
is working.
Then we create a new instance of VideoPlayerApp
and call play
on it as follows:
const videoPlayerApp = new VideoPlayerApp();
videoPlayerApp.play();
After that, we get our mock VideoPlayer
instance by running:
const mockVideoPlayer = VideoPlayer.mock.instances[0];
Then we call the playVideoFile
method of the VideoPlayer
class that was mocked as follows:
const mockPlayVideoFile = mockVideoPlayer.playVideoFile;
Then we can get the first argument that’s passed into the mock playVideoFile
call by using:
mockPlayVideoFile.mock.calls[0][0]
We can then check that the call was done with 'video.mp4'
passed by writing:
expect(mockPlayVideoFile.mock.calls[0][0]).toEqual(videoFileName);
Equivalently, we can write:
expect(mockPlayVideoFile).toHaveBeenCalledWith(videoFileName);
Finally, we check that the playVideoFile
method was called only once by running:
expect(mockPlayVideoFile).toHaveBeenCalledTimes(1);
This makes sense since playVideoFile
is only called once in our test.
Photo by Andrew Tanglao on Unsplash
Running the Tests
Be sure to install Jest by running:
npm i jest
and put:
"test": "jest"
in the scripts
section so we can run the tests.
In the end, we should get:
PASS ./app.test.js
√ VideoPlayer is called once (3ms)
√ VideoPlayer is called with video.mp4 (2ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.067s
Ran all test suites.
Conclusion
With Jest’s automatic mocks, we can mock classes or constructor functions easily.
All methods are mocked with functions that return undefined
.
Then we can retrieve the mock by using mockedObject.mock.instances
, which is an array.
Then we can run checks as usual. with toEqual
, toHaveBeenCalledWith
, and other matcher methods.