How can I test a React Hooks component by changing useState - reactjs

I have a React hooks functional component that I'd like to test with Jest/Enzyme. I would like test its tertiary render behaviour based upon a useState value. I can't seem to find any example online. There is no 'click' to simulate - no API call to mock because at the end, I still need to test based upon the useState value.
In the past, with class components, I could set the state. With the new hooks, I can't.
So, basically - how do I mock an async await inside a mocked submitForm function so that the render behaves properly?
Here's my component:
import React, { useState } from 'react';
import { Redirect } from 'react-router-dom';
import Form from 'core/Form';
export const Parent = ({submitForm}) => {
const [formValues, setFormValues] = useState({});
const [redirect, setRedirect] = useState(false);
const handleChange = name => evt => {
setFormValues({ ...formValues, [name]: evt.target.value });
};
const onSubmit = async () => {
try {
const res = await submitForm(formValues);
if (res) setRedirect(true);
else setRedirect(false);
} catch (err) {
console.log('Submit error: ', err);
}
};
return redirect ? (
<Redirect push to={path} />
) : (
<Form onSubmit={onSubmit} values={formValues} onChange={handleChange} />
);
};
export default Parent;
Here's my testing so far:
import React from 'react';
import { shallow } from 'enzyme';
import { Redirect } from 'react-router-dom';
import Parent from './Parent';
import Form from 'core/Form';
let wrapper, props;
.
.
.
describe('<Parent /> rendering', () => {
beforeEach(() => {
props = createTestProps();
wrapper = shallow(<Parent {...props} />);
});
afterEach(() => {
jest.clearAllMocks();
});
const setState = jest.fn();
const useStateSpy = jest.spyOn(React, 'useState');
useStateSpy.mockImplementation(init => [init, setState]);
it('Should render 1 Form', () => {
expect(wrapper.find(Form)).toHaveLength(1);
});
it('renders Redirect after API call', () => {
setRedirect = jest.fn(() => false);
expect(wrapper.find(Redirect)).toHaveLength(1);
});
it('renders Form before API call', () => {
setRedirect = jest.fn(() => true);
expect(wrapper.find(Form)).toHaveLength(1);
});
});

You don't need to spy useState hook. Which means you should not test these hooks and methods of the component directly. Instead, you should test components' behavior(the state, props and what is rendered)
E.g.
index.tsx:
import React, { useState } from 'react';
import { Redirect } from 'react-router-dom';
export const Form = ({ onSubmit, onChange, values }) => <form onSubmit={onSubmit}></form>;
const path = '/user';
export const Parent = ({ submitForm }) => {
const [formValues, setFormValues] = useState({});
const [redirect, setRedirect] = useState(false);
const handleChange = (name) => (evt) => {
setFormValues({ ...formValues, [name]: evt.target.value });
};
const onSubmit = async () => {
try {
const res = await submitForm(formValues);
if (res) setRedirect(true);
else setRedirect(false);
} catch (err) {
console.log('Submit error: ', err);
}
};
return redirect ? (
<Redirect push to={path} />
) : (
<Form onSubmit={onSubmit} values={formValues} onChange={handleChange} />
);
};
export default Parent;
index.test.tsx:
import Parent, { Form } from './';
import React from 'react';
import { shallow } from 'enzyme';
import { Redirect } from 'react-router-dom';
import { act } from 'react-dom/test-utils';
const whenStable = async () =>
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
describe('60137762', () => {
it('should render Form', () => {
const props = { submitForm: jest.fn() };
const wrapper = shallow(<Parent {...props}></Parent>);
expect(wrapper.find(Form)).toBeTruthy();
});
it('should handle submit and render Redirect', async () => {
const props = { submitForm: jest.fn().mockResolvedValueOnce(true) };
const wrapper = shallow(<Parent {...props}></Parent>);
wrapper.find(Form).simulate('submit');
await whenStable();
expect(props.submitForm).toBeCalledWith({});
expect(wrapper.find(Redirect)).toBeTruthy();
});
it('should handle submit and render Form', async () => {
const props = { submitForm: jest.fn().mockResolvedValueOnce(false) };
const wrapper = shallow(<Parent {...props}></Parent>);
wrapper.find(Form).simulate('submit');
await whenStable();
expect(props.submitForm).toBeCalledWith({});
expect(wrapper.find(Form)).toBeTruthy();
});
it('should handle error if submit failure', async () => {
const logSpy = jest.spyOn(console, 'log');
const mError = new Error('network');
const props = { submitForm: jest.fn().mockRejectedValueOnce(mError) };
const wrapper = shallow(<Parent {...props}></Parent>);
wrapper.find(Form).simulate('submit');
await whenStable();
expect(props.submitForm).toBeCalledWith({});
expect(logSpy).toHaveBeenCalledWith('Submit error: ', mError);
});
});
Unit test results with coverage report:
PASS stackoverflow/60137762/index.test.tsx
60137762
✓ should render Form (18ms)
✓ should handle submit and render Redirect (15ms)
✓ should handle submit and render Form (8ms)
✓ should handle error if submit failure (18ms)
console.log node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
Submit error: Error: network
at /Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:39:20
at step (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:44:23)
at Object.next (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:25:53)
at /Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:19:71
at new Promise (<anonymous>)
at Object.<anonymous>.__awaiter (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:15:12)
at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/stackoverflow/60137762/index.test.tsx:37:47)
at Object.asyncJestTest (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:100:37)
at resolve (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
at new Promise (<anonymous>)
at mapper (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
at promise.then (/Users/ldu020/workspace/github.com/mrdulin/react-apollo-graphql-starter-kit/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 78.57 | 100 | 40 | 93.75 |
index.tsx | 78.57 | 100 | 40 | 93.75 | 12
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 3.716s, estimated 5s
Source code: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/60137762

Related

React testing library - testing setState inside Promise then caused An update to TestComponent inside a test was not wrapped in act

I'm trying to test a custom hook which setting a state in a Promise then/catch block.
This is my custom hook:
import {useState} from 'react';
import {useBackendApi} from './backend-api';
export function useMyCustomHook(): {status?: Status; loadData: () => void} {
const [status, setStatus] = useState<Status>();
const backendApi = useBackendApi();
const loadData = (): void => {
backendApi.makeApiCall().then(() => {
setStatus(Status.SUCCESS);
}).catch(() => {
setStatus(Status.ERROR);
});
};
return {status, loadData};
}
export enum Status {
SUCCESS = 1,
ERROR = 2,
}
And these are my tests:
import {act, renderHook, waitFor} from '#testing-library/react';
import * as BackendApiHooks from './backend-api';
import {Status, useMyCustomHook} from './my-custom-hook';
describe('useMyCustomHook', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should set the status to SUCCESS when makeApiCall resolved', async () => {
jest.spyOn(BackendApiHooks, 'useBackendApi').mockReturnValue({
makeApiCall: jest.fn().mockReturnValue(Promise.resolve()),
});
const {result} = renderHook(() => useMyCustomHook());
await act(() => {
result.current.loadData();
});
await waitFor(() => expect(result.current.status).toBe(Status.SUCCESS));
});
it('should set the status to ERROR when makeApiCall rejected', async () => {
jest.spyOn(BackendApiHooks, 'useBackendApi').mockReturnValue({
makeApiCall: jest.fn().mockReturnValue(Promise.reject()),
});
const {result} = renderHook(() => useMyCustomHook());
await act(() => {
result.current.loadData();
});
await waitFor(() => expect(result.current.status).toBe(Status.ERROR));
});
});
My tests are passed but on the first test there is an error printed to the console:
Warning: An update to TestComponent inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at TestComponent (/****/node_modules/#testing-library/react/dist/pure.js:281:5)
8 | const loadData = (): void => {
9 | backendApi.makeApiCall().then(() => {
> 10 | setStatus(Status.SUCCESS);
| ^
11 | }).catch(() => {
12 | setStatus(Status.ERROR);
13 | });
at printWarning (node_modules/react-dom/cjs/react-dom.development.js:86:30)
at error (node_modules/react-dom/cjs/react-dom.development.js:60:7)
at warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27589:9)
at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25508:5)
at dispatchSetState (node_modules/react-dom/cjs/react-dom.development.js:17527:7)
at src/modules/table/components/my-custom-hook.ts:10:13
It happens although I wrapped the result.current.loadData() in act.
Important note: It happens only on the first test (with the resolve mock) but not on the second test (with the reject mock)
Couldn't find any relevant solution in other questions. What am I missing?

React Testing With Jest - Mock components on the same file except one

I have a tsx file which contains three react components:
import {FC} from 'react';
export const ComponentA: FC<{booleanProp: boolean}> = ({booleanProp}) => {
return (
<>
{booleanProp ? (
<ComponentB />
) : (
<ComponentC />
)}
</>
);
};
export const ComponentB: FC = () => {
return <span>ComponentB</span>;
};
export const ComponentC: FC = () => {
return <span>ComponentC</span>;
};
I want to test ComponentA and mock ComponentB and ComponentC.
This is my test file:
import {FC} from 'react';
import {createRoot, Root} from 'react-dom/client';
import {act} from 'react-dom/test-utils';
import {ComponentA} from './my-components';
jest.mock('./my-components', () => {
const ComponentBMock: FC = () => {
return <span>ComponentB Mock</span>;
};
const ComponentCMock: FC = () => {
return <span>ComponentC Mock</span>;
};
return {
...jest.requireActual('./my-components'),
ComponentB: ComponentBMock,
ComponentC: ComponentCMock,
};
});
describe('ComponentA', () => {
let container: Element | null = null;
let root: Root | null = null;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => {
root?.unmount();
root = null;
});
container?.remove();
container = null;
});
it('should render "ComponentB" when booleanProp is true', () => {
act(() => {
root?.render(<ComponentA booleanProp={true}/>);
});
expect(container?.textContent).toEqual('ComponentB Mock');
});
it('should render "ComponentC" when booleanProp is false', () => {
act(() => {
root?.render(<ComponentA booleanProp={false}/>);
});
expect(container?.textContent).toEqual('ComponentC Mock');
});
});
The problem is that the mocks doesn't seem to take effect and these are the tests result:
Expected: "ComponentB Mock"
Received: "ComponentB"
Expected: "ComponentC Mock"
Received: "ComponentC"
When I debugged the jest mock callback it appears to be called twice. In the first time the requireActual returned undefined for every component, and in the second time it has the real components values.
What am I missing?
Thanks for your help!
So after playing with it and read more about mocking this solution solved my problem.
The difference is the way I import my components and used jest.spyOn to mock them:
import * as MyComponents from './my-components';
const ComponentA = MyComponents.ComponentA;
jest.spyOn(MyComponents, 'ComponentB').mockReturnValue(<span>ComponentB Mock</span>);
jest.spyOn(MyComponents, 'ComponentC').mockReturnValue(<span>ComponentC Mock</span>);
Of course that if you you need to remove the created mock you can restore the mock by calling the spy.mockRestore function.
The full test file:
import {createRoot, Root} from 'react-dom/client';
import {act} from 'react-dom/test-utils';
import * as MyComponents from './my-components';
const ComponentA = MyComponents.ComponentA;
jest.spyOn(MyComponents, 'ComponentB').mockReturnValue(<span>ComponentB Mock</span>);
jest.spyOn(MyComponents, 'ComponentC').mockReturnValue(<span>ComponentC Mock</span>);
describe('ComponentA', () => {
let container: Element | null = null;
let root: Root | null = null;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => {
root?.unmount();
root = null;
});
container?.remove();
container = null;
});
it('should render "ComponentB" when booleanProp is true', () => {
act(() => {
root?.render(<ComponentA booleanProp={true}/>);
});
expect(container?.textContent).toEqual('ComponentB Mock');
});
it('should render "ComponentC" when booleanProp is false', () => {
act(() => {
root?.render(<ComponentA booleanProp={false}/>);
});
expect(container?.textContent).toEqual('ComponentC Mock');
});
});

Write the test case for useEffect with Fetch API and useState in react

Following are my code which includes the fetch API(getData) call with the useEffect and once get the response it will set the result into the setData using useState
I am trying to write the test case for the useEffect and useState but its failing and when I am seeing into the coverage ,I am getting the red background color with statements not covered for the useEffect block.
import { getData } from '../../api/data';
const [data, setData] = useState({});
useEffect(() => {
getData({ tableName }).then((response) => {
try {
if (response && response.result) {
const result = Array.isArray(response.result)
? response.result[0]
: response.result;
const createDate = result.createdDate;
result.name = result.firstName;
result.submittedDate = `${createDate}`;
result.attribute = Array.isArray(result.attribute)
? result.attribute
: JSON.parse(result.attribute);
setData(result);
}
} catch (error) {
const errorObj = { error: error.message || 'error' };
setData({ errorObj });
}
});
}, []);
And I tried to write the test cases as following for the above code.
import React from "react";
import {
shallowWithIntl,
loadTranslation,
} from "../../../node_modules/enzyme-react-intl/lib/enzyme-react-intl";
import ParentPage from "ParentPage";
import ChildPage from "ChildPage";
import mockResponse from "mockData";
import { shallow, mount } from "enzyme";
import { act } from "react-dom/test-utils";
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(mockResponse),
})
);
describe("ParentPage", () => {
let useEffect;
let wrapper;
const mockUseEffect = () => {
useEffect.mockImplementationOnce((f) => f());
};
beforeEach(() => {
const defaultProps = {
tableName: "tableName",
};
wrapper = shallowWithIntl(<ParentPage {...defaultProps} />);
useEffect = jest.spyOn(React, "useEffect");
mockUseEffect();
});
it("Should render", () => {
expect(wrapper).toMatchSnapshot();
});
it("Compenent render", async () => {
let wrapper;
await act(async () => {
const setWidgets = jest.fn();
const useStateSpy = jest.spyOn(React, "useState");
useStateSpy.mockImplementation([mockResponse, setWidgets]);
wrapper = await mount(<ChildPage data={mockResponse} />);
await act(async () => {
wrapper.update();
});
console.log(wrapper);
});
});
});
But when I tried using npm run test,And check the coverage I am still getting the statements not covered for the useEffect and useState.
What should I do to achieve the coverage as maximum as possible?

Test if function have been called - React,

I am trying to test a simple component.
In my component, I am calling the fetchPdf function with useEffect.
Inside fetchPdf function I am making axios request and if the call was successful I am setting the response (which is the pdf) in the state.
My component:
import React, { useState, useEffect } from "react";
import axios from 'axios';
export default function App() {
const [pdf, setPdf] = useState();
const fetchPdf = () => {
// Here I am make an API call - on success I am
// set the pdf from the response into state
// axios.get('url/endpoint')
// .then((res) => {
// if (res.status === 200) {
// setPdf(res.data);
// }
// }).catch((e) => console.log(e.message));
setPdf("Mocked PDF");
};
useEffect(() => {
fetchPdf();
}, []);
return (
<div className="App">
<h1>My pdf from state: {pdf}</h1>
</div>
);
}
My test:
import React from "react";
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import App from "./App";
Enzyme.configure({ adapter: new Adapter() });
describe("<App />", () => {
let wrapper;
const setState = jest.fn();
const useStateSpy = jest.spyOn(React, "useState");
useStateSpy.mockImplementation((init) => [init, setState]);
beforeEach(() => {
wrapper = Enzyme.shallow(<App />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe("calling designPDF", () => {
it("set designPDF into the state", () => {
// Not sure how can I test the fetchPDF function as been called
});
});
});
Codesendbox example
useEffect does not support shallow rendering yet. So you should use Full Rendering API (mount(...)).
Besides, you should mock axios.get method and its resolved value. Call the whenStable function to ensure that the promise returned by the axios.get method is resolved or rejected
E.g.
App.jsx:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
export default function App() {
const [pdf, setPdf] = useState();
const fetchPdf = () => {
axios
.get('url/endpoint')
.then((res) => {
if (res.status === 200) {
setPdf(res.data);
}
})
.catch((e) => console.log(e.message));
};
useEffect(() => {
fetchPdf();
}, []);
return (
<div className="App">
<h1>My pdf from state: {pdf}</h1>
</div>
);
}
App.test.jsx:
import App from './App';
import axios from 'axios';
import { mount } from 'enzyme';
import React from 'react';
import { act } from 'react-dom/test-utils';
const whenStable = async () => {
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0));
});
};
describe('65310275', () => {
it('should get pdf', async () => {
const getSpy = jest.spyOn(axios, 'get').mockResolvedValueOnce({ data: 'Mocked PDF', status: 200 });
const wrapper = mount(<App></App>);
await whenStable();
expect(wrapper.find('h1').text()).toEqual('My pdf from state: Mocked PDF');
expect(getSpy).toBeCalledWith('url/endpoint');
getSpy.mockRestore();
});
it('should handle error', async () => {
const mErr = new Error('timeout');
const getSpy = jest.spyOn(axios, 'get').mockRejectedValueOnce(mErr);
const logSpy = jest.spyOn(console, 'log');
const wrapper = mount(<App></App>);
await whenStable();
expect(wrapper.find('h1').text()).toEqual('My pdf from state: ');
expect(getSpy).toBeCalledWith('url/endpoint');
expect(logSpy).toBeCalledWith('timeout');
getSpy.mockRestore();
});
});
unit test result:
PASS examples/65310275/App.test.jsx
65310275
✓ should get pdf (40 ms)
✓ should handle error (13 ms)
console.log
timeout
at CustomConsole.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 50 | 100 | 100 |
App.jsx | 100 | 50 | 100 | 100 | 11
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 6.095 s
source code: https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/65310275

Warning: An update to App inside a test was not wrapped in act(...) in enzyme and hooks

I have written this component. it fetchs data using hooks and state. Once it is fetched the loading state is changed to false and show the sidebar.
I faced a problem with Jest and Enzyme, as it does throw a warning for Act in my unit test. once I add the act to my jest and enzyme the test is failed!
// #flow
import React, { useEffect, useState } from 'react';
import Sidebar from '../components/Sidebar';
import fetchData from '../apiWrappers/fetchData';
const App = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const getData = async () => {
try {
const newData = await fetchData();
setData(newData);
setLoading(false);
}
catch (e) {
setLoading(false);
}
};
getData();
// eslint-disable-next-line
}, []);
return (
<>
{!loading
? <Sidebar />
: <span>Loading List</span>}
</>
);
};
export default App;
And, I have added a test like this which works perfectly.
import React from 'react';
import { mount } from 'enzyme';
import fetchData from '../apiWrappers/fetchData';
import data from '../data/data.json';
import App from './App';
jest.mock('../apiWrappers/fetchData');
const getData = Promise.resolve(data);
fetchData.mockReturnValue(getData);
describe('<App/> Rendering using enzyme', () => {
beforeEach(() => {
fetchData.mockClear();
});
test('After loading', async () => {
const wrapper = mount(<App />);
expect(wrapper.find('span').at(0).text()).toEqual('Loading List');
const d = await fetchData();
expect(d).toHaveLength(data.length);
wrapper.update();
expect(wrapper.find('span').exists()).toEqual(false);
expect(wrapper.html()).toMatchSnapshot();
});
});
So, I got a warning:
Warning: An update to App inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
I did resolve the warning like this using { act } react-dom/test-utils.
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import fetchData from '../apiWrappers/fetchData';
import data from '../data/data.json';
import App from './App';
jest.mock('../apiWrappers/fetchData');
const getData = Promise.resolve(data);
fetchData.mockReturnValue(getData);
describe('<App/> Rendering using enzyme', () => {
beforeEach(() => {
fetchData.mockClear();
});
test('After loading', async () => {
await act(async () => {
const wrapper = mount(<App />);
expect(wrapper.find('span').at(0).text()).toEqual('Loading List');
const d = await fetchData();
expect(d).toHaveLength(data.length);
wrapper.update();
expect(wrapper.find('span').exists()).toEqual(false);
expect(wrapper.html()).toMatchSnapshot();
});
});
});
But, then my test is failed.
<App/> Rendering using enzyme › After loading
expect(received).toEqual(expected) // deep equality
Expected: false
Received: true
35 |
36 | wrapper.update();
> 37 | expect(wrapper.find('span').exists()).toEqual(false);
Does anybody know why it fails? Thanks!
"react": "16.13.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",
This issue is not new at all. You can read the full discussion here: https://github.com/enzymejs/enzyme/issues/2073.
To sum up, currently in order to fix act warning, you have to wait a bit before update your wrapper as following:
const waitForComponentToPaint = async (wrapper) => {
await act(async () => {
await new Promise(resolve => setTimeout(resolve));
wrapper.update();
});
};
test('After loading', async () => {
const wrapper = mount(<App />);
expect(wrapper.find('span').at(0).text()).toEqual('Loading List');
// before the state updated
await waitForComponentToPaint(wrapper);
// after the state updated
expect(wrapper.find('span').exists()).toEqual(false);
expect(wrapper.html()).toMatchSnapshot();
});
You should not wrap your whole test in act, just the part that will cause state of your component to update.
Something like the below should solve your problem.
test('After loading', async () => {
await act(async () => {
const wrapper = mount(<App />);
});
expect(wrapper.find('span').at(0).text()).toEqual('Loading List');
const d = await fetchData();
expect(d).toHaveLength(data.length);
await act(async () => {
wrapper.update();
})
expect(wrapper.find('span').exists()).toEqual(false);
expect(wrapper.html()).toMatchSnapshot();
});

Resources