Test React Custom Hooks with Jest and Testing Library

  1. Prerequisite Installation
  2. Custom Hook
  3. Test Custom Hook

Prerequisite Installation

Assume that we already installed Jest, Testing Library and SWR.

Custom Hook

If you don’t know what is the custom hooks we recommend you to read the official document about custom hooks.

Before we write the test we should have a custom hook first.

We create a custom hook useIndex to fetch the data from API as follow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import useSWR from "swr";

const fetcher = (url) => fetch(url).then((res) => res.json());

export const useIndex = () => {
const { data, error } = useSWR(
"https://api.github.com/repos/vercel/swr",
fetcher
);

return {
data,
error,
};
};

Then we use useIndex as follow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from "react";

import { useIndex } from "./hook";

export default function Index() {
const { data, error } = useIndex();

if (error) return "An error has occurred.";

if (!data) return "Loading...";

return (
<>
<h1>{data.name}</h1>
<strong>{data.subscribers_count}</strong> <strong>
{data.stargazers_count}
</strong> <strong>{data.forks_count}</strong>
</>
);
}

Test Custom Hook

Beforehand

There are one property and one function we need to know in Jest:

  • jest.mock – used to spied on the behavior of a function.
  • mockImplementation – used as the implementation of the mock.

There is one function we need to know in Testing Library:

  • render – used to render the component virtually.

Case 1

The first case is that we want to check “Loading…” is displayed when undefined data is returned from the API.

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
import '@testing-library/jest-dom';
import '@testing-library/jest-dom/extend-expect';

import { jest, test } from '@jest/globals';
import { render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';

import { useIndex } from './hook';
import IndexPage from './index';

jest.mock('./hook', () => ({
useIndex: jest.fn(() => ({
data: {},
error: undefined,
})),
}));

test('Assert "Loading..." is displayed when undefined data is returned from the API', () => {
(useIndex as any).mockImplementation(() => ({
data: undefined,
error: undefined,
}));

const { getByText } = render(
<BrowserRouter>
<Page />
</BrowserRouter>,
);

expect(getByText('Loading...')).toBeInTheDocument();
});

Case 2

The second case is that we want to check “An error has occurred.” is displayed when an error is returned from the API.

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
import '@testing-library/jest-dom';
import '@testing-library/jest-dom/extend-expect';

import { jest, test } from '@jest/globals';
import { render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';

import { useIndex } from './hook';
import IndexPage from './index';

jest.mock('./hook', () => ({
useIndex: jest.fn(() => ({
data: {},
error: undefined,
})),
}));

test('Assert "An error has occurred." is displayed when an error is returned from the API', () => {
(useIndex as any).mockImplementation(() => ({
data: {},
loadingError: { response: { status: 500 } },
}));

const { getByText } = render(
<BrowserRouter>
<Page />
</BrowserRouter>,
);

expect(getByText('An error has occurred.')).toBeInTheDocument();
});

Case 3

And the third case is that we want to check data is displayed when there is no error.

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
33
34
35
36
37
38
39
40
import '@testing-library/jest-dom';
import '@testing-library/jest-dom/extend-expect';

import { jest, test } from '@jest/globals';
import { render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';

import { useIndex } from './hook';
import IndexPage from './index';

jest.mock('./hook', () => ({
useIndex: jest.fn(() => ({
data: {},
error: undefined,
})),
}));

test('Assert data is displayed when there is no error', () => {
(useIndex as any).mockImplementation(() => ({
data: {
id: 218115303,
name: 'swr',
description: 'React Hooks for Data Fetching',
full_name: 'vercel/swr',
owner: {
id: 14985020,
login: 'vercel'
}
},
loadingError: undefined,
}));

const { getByText } = render(
<BrowserRouter>
<Page />
</BrowserRouter>,
);

expect(getByText('swr')).toBeInTheDocument();
});

Source: https://engineering.mobalab.net/2022/12/13/test-react-custom-hooks-with-jest-and-testing-library/