Here is the unit test solution:
We use jest.spyOn(axios, 'get')
to mock axios.get
method and its resolved/rejected value without hitting the real network. This allows our unit tests to run in an environment that has no side effects and is isolated from the system environment, network environment, etc.
We use act() helper to make sure the fetched data rendered and UI has been updated.
When writing UI tests, tasks like rendering, user events, or data fetching can be considered as “units” of interaction with a user interface. react-dom/test-utils provides a helper called act() that makes sure all updates related to these “units” have been processed and applied to the DOM before you make any assertions:
In the end, We assert whether the axios.get
method is called, and through snapshot testing, assert whether data
is rendered correctly
index.tsx
:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export const DisplayData = ({ query, onQueryChange }) => {
const [data, setData] = useState<any>({ hits: [] });
useEffect(() => {
const fetchData = async () => {
const result = await axios.get(`http://hn.algolia.com/api/v1/search?query=${query}`);
setData(result.data);
};
if (!!query) fetchData();
}, [query]);
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
};
index.spec.tsx
:
import React from 'react';
import { DisplayData } from './';
import axios from 'axios';
import renderer, { act } from 'react-test-renderer';
describe('DisplayData', () => {
it('should show new entries when query is set', async () => {
const mProps = {
query: 'pippo',
onQueryChange: jest.fn()
};
const FAKE_HITS = [{ objectID: 1, url: 'haha.com', title: 'haha' }];
const axiosGetSpy = jest.spyOn(axios, 'get').mockResolvedValueOnce({ data: { hits: FAKE_HITS } });
let component;
await act(async () => {
component = renderer.create(<DisplayData {...mProps}></DisplayData>);
});
expect(axiosGetSpy).toBeCalledWith('http://hn.algolia.com/api/v1/search?query=pippo');
expect(component.toJSON()).toMatchSnapshot();
axiosGetSpy.mockRestore();
});
it('should not fetch data when query is empty string', async () => {
const mProps = {
query: '',
onQueryChange: jest.fn()
};
const axiosGetSpy = jest.spyOn(axios, 'get');
let component;
await act(async () => {
component = renderer.create(<DisplayData {...mProps}></DisplayData>);
});
expect(axiosGetSpy).not.toBeCalled();
expect(component.toJSON()).toMatchSnapshot();
axiosGetSpy.mockRestore();
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/56410688/index.spec.tsx
DisplayData
? should show new entries when query is set (28ms)
? should not fetch data when query is empty string (5ms)
-----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.tsx | 100 | 100 | 100 | 100 | |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 2 passed, 2 total
Time: 3.666s
index.spec.tsx.snap
:
// Jest Snapshot v1,
exports[`DisplayData should not fetch data when query is empty string 1`] = `<ul />`;
exports[`DisplayData should show new entries when query is set 1`] = `
<ul>
<li>
<a
href="haha.com"
>
haha
</a>
</li>
</ul>
`;
Dependencies versions:
"jest": "^24.9.0",
"react-test-renderer": "^16.11.0",
"react": "^16.11.0",
"axios": "^0.19.0",
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/56410688