JavaScript testing #10. Advanced mocking with Jest and React Testing Library
April 18, 2022 In the fourth part of this series, we’ve learned the basics of mocking API calls. However, there are often situations where we would like to test
9.8k
By Kate Angelou
In the fourth part of this series, we’ve learned the basics of mocking API calls. However, there are often situations where we would like to test various more demanding cases. So, in this article, we implement more advanced examples on how to mock with Jest.
Creating a simple React component
Let’s start by creating a straightforward React component that renders a list of posts.
Above, we use jest.mock() function that creates a mock function. For example, we can use it to change the value that a function returns. In our case, we force the fetchPosts function to return a promise that resolves to an empty array.
Mocking a default export
Our original fetchPosts.tsx file uses a default export. Therefore, we need to modify our mock a bit.
We can simplify the above mock a little by using the mockResolvedValue method.
Posts.test.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import{render}from'@testing-library/react';
import React from'react';
import Posts from'./Posts';
jest.mock('./fetchPosts',()=>({
__esModule:true,
default:jest.fn().mockResolvedValue([]),
}));
describe('The Posts component',()=>{
describe('when the component fetches an empty array of posts',()=>{
it('should render the no posts indicator',async()=>{
constposts=render(<Posts/>);
await posts.findByTestId('posts-empty');
});
});
});
The fetchPosts returns a promise that resolves to an empty array thanks to the above. Because of that, the Posts component does not make an actual HTTP request.
A crucial thing to notice in our test is that our fetchPosts function is still asynchronous. Since we use await posts.findByTestId('posts-empty'), our test waits up to 1000ms for the <div data-testid="posts-empty"/> to appear. If the React Testing Library does not find the element during that time, it throws an error, and our test fails.
Mocking a function differently per test
Above, we create a mocked version of the fetchPosts function that always returns the same value. Instead, we might want to mock a function differently per test. Let’s start by using the jest.mock() function on top of our test.
jest.mock('./fetchPosts',()=>({
__esModule:true,
default:jest.fn()
}));
Thanks to doing the above, our fetchPosts function is now replaced with a mock function. The crucial thing to acknowledge is that when we import fetchPosts in usePostsLoading.tsx or post.test.tsx, we import an instance of a mock function. So we can use this fact and interact with our mock through the test to change what it returns in usePostsLoading.tsx.
Posts.test.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import{render}from'@testing-library/react';
import React from'react';
import Posts from'./Posts';
import fetchPosts from'./fetchPosts';
jest.mock('./fetchPosts',()=>({
__esModule:true,
default:jest.fn(),
}));
describe('The Posts component',()=>{
describe('when the component fetches an empty array of posts',()=>{
beforeEach(()=>{
(fetchPosts asjest.Mock).mockResolvedValue([]);
});
it('should render the no posts indicator',async()=>{
constposts=render(<Posts/>);
await posts.findByTestId('posts-empty');
});
});
describe('when the posts fetching fails',()=>{
beforeEach(()=>{
(fetchPosts asjest.Mock).mockRejectedValue(null);
});
it('should render the error indicator',async()=>{
constposts=render(<Posts/>);
await posts.findByTestId('posts-error');
});
});
});
Since the fetchPosts function that we import on top of our test now has a value of jest.fn(), we can call mockResolvedValue and mockRejectedValue on it. We can achieve a different mocked value per test by manipulating the fetchPosts mock before each test.
Mocking a React component
When writing tests with Enzyme, we can choose to either render a component with all of its children or perform a shallow render. In contrast, we don’t have this option with React Testing Library.
The idea behind React Testing Library is to write tests that resemble the way the users interact with our application. Because of that, mocking React components is discouraged. Even though that’s the case, we sometimes might want to avoid testing some components as a whole. The above might happen when using third-party libraries, for example.
Without mocking the react-json-view library above, we wouldn’t have a straightforward way of checking whether our component rendered it when using React Testing Library.
Mocking a module partially
So far, we’ve constantly mocked a whole module when using jest.mock(). We might not want to do that in every case. Let’s imagine having the following file:
If we want to mock only the sum function, we can pair jest.mock() with jest.requireActual(). When we call jest.requireActual(), we can retrieve the original content of a module even if we mocked it.
jest.mock('./utilities',()=>({
...jest.requireActual('./utilities'),
sum:jest.fn(),
}));
Summary
In this article, we’ve gone through various use-cases of mocking functions. This included real-life situations such as mocking a function differently per test, mocking a default export, or mocking a React component. We’ve also learned how to mock a module partially. All of the above provides a cheat sheet that might come in handy in various cases.