error when creating multiple asynchronous tests with act function jest js - reactjs

in this case I am doing an example project which tries to show a phrase and the author of the phrase the first time the component is loaded and when a button is clicked to obtain a new phrase. The problem with testing is I am trying to simulate the way the user interacts with the application and having two tests asynchronously where each one has the act () function I get the following error:
console.error node_modules/react-dom/cjs/react-dom-test-utils.development.js:87
Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one.
console.error node_modules/react-dom/cjs/react-dom.development.js:88
Warning: An update to QuoteContainer 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.
in QuoteContainer (created by WrapperComponent)
in WrapperComponent
console.error node_modules/react-dom/cjs/react-dom.development.js:88
Warning: An update to QuoteContainer 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.
in QuoteContainer (created by WrapperComponent)
in WrapperComponent
console.error node_modules/react-dom/cjs/react-dom.development.js:88
Warning: An update to QuoteContainer 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.
in QuoteContainer (created by WrapperComponent)
in WrapperComponent
The test is passing but it generates this alert. I already looked for alternatives such as avoiding asynchronous testing but when doing it the test does not pass, it forces me to put the await in the act.
This example is simple but I would like to apply this same test in more complex applications to verify the correct operation of it. So it is probably necessary to have more asynchronous tests.
Here is the container component:
import React, {
useEffect,
useState,
} from "react";
import Quote from "./quote.component";
import { getQuote } from "../../repository/quote.repository";
const QuoteContainer = () => {
const [quote, setQuote] = useState("");
const [author, setAuthor] = useState("");
const [isLoading, setLoading] = useState(true);
useEffect(() => {
getQuoteData();
}, []);
const newQuoteHandler = () => {
getQuoteData();
};
const getQuoteData = async () => {
const {
quoteText,
quoteAuthor,
} = await getQuote();
setAuthor(quoteAuthor);
setQuote(quoteText);
setLoading(false);
};
return (
<Quote
isLoading={isLoading}
quote={quote}
author={author}
newQuoteHandler={newQuoteHandler}
/>
);
};
export default QuoteContainer;
And the test related to the container:
import React from "react";
import { server, rest } from "../../mocks/server";
import waitForExpect from "wait-for-expect";
import { mount } from "../../enzymeConfig";
import QuoteContainer from "./quote.container";
import { act } from "react-dom/test-utils";
import { QuoteText } from "../quote/quote.style";
import { SpinnerContainer } from "../withSpinner/withSpinner.style";
describe("testing quote api", () => {
it("should render spinner on start", () => {
const wrapper = mount(<QuoteContainer />);
expect(
wrapper.find(SpinnerContainer)
).toHaveLength(1);
});
it("should render actual component on load information", async (done) => {
expect.assertions(1);
const wrapper = mount(<QuoteContainer />);
await act(async () => {
await waitForExpect(() => {
wrapper.update();
expect(
wrapper.find(QuoteText).text()
).toEqual(
"Know how to listen, and you will profit even from those who talk badly."
);
done();
});
});
});
it("should change quote and author when Quote Button click", async (done) => {
const wrapper = mount(<QuoteContainer />);
await act(async () => {
await waitForExpect(() => {
wrapper.update();
expect(
wrapper.find(QuoteText).text()
).toEqual(
"Know how to listen, and you will profit even from those who talk badly."
);
done();
});
});
rest.get(
"apiURL",
(req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
quoteText: "this is an other frase",
quoteAuthor: "Plutarch ",
senderName: "",
senderLink: "",
})
)
);
wrapper
.find(CustomButton)
.at(0)
.simulate("click");
await act(async () => {
await waitForExpect(() => {
wrapper.update();
expect(
wrapper.find(QuoteText).text()
).toEqual("this is an other frase");
done();
});
});
});
});
Thanks from now

Related

An update to BrowserRouter inside a test was not wrapped in act

I'm trying to implement my first tests in react with react-test-library, but I came across this certain problem where there's warning that my component is not wrapped in act(..)
down below is the test that I'm trying to implement
import { BrowserRouter as Router } from "react-router-dom";
beforeEach(() => {
container = render(
<Router>
<Search />
</Router>
);
});
it("handleClick", async () => {
const button = container.getByText("Search");
const event = fireEvent.click(button);
expect(event).toBeTruthy();
});
and Here is the function that I'm trying to test
const handleClick = async () => {
setLoading(true);
const data = await movieAPI.fetchMovieByTitle(movie);
setLoading(false);
navigate(`/movie/${data.Title}`, { state: data });
};
Turns out that before doing the assertions we need to wait for the component update to fully complete with waitFor
it("should render spinner", async () => {
const button = container.getByText("Search");
const event = await fireEvent.click(button);
const spinner = container.getByTestId("spinner");
await waitFor(() => {
expect(spinner).toBeInTheDocument();
});
});

How to correctly test React with act

I'm trying to test a component but it errors with
console.error
Warning: An update to Example 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 Example (/Users/thr15/Developmemt/Boost/basket-creator/frontend/src/Page/F1/Example.tsx:5:29)
at WrapperComponent (/Users/thr15/Developmemt/Boost/basket-creator/frontend/node_modules/enzyme-adapter-utils/src/createMountWrapper.jsx:49:26)
Here's a simplified version of my component
import {useState} from 'react';
function Example(): JSX.Element {
const [name, setName] = useState('');
const [, setLoading] = useState(false);
const [results, setResults] = useState<number[]>([]);
/**
* Search the baskets.
*/
const search = async () => {
// Let the UI know we're loading
setLoading(true);
// Get the baskets
try {
const baskets: number[] = await (await fetch('/test?name=' + name)).json();
// Give the UI the data
setLoading(false);
setResults(baskets);
} catch (e) {
console.error(e);
}
};
return <div className={"content"}>
<input value={name} onChange={(e) => setName(e.target.value)}/>
<button onClick={search}>Search</button>
{results.length}
</div>
}
export default Example;
and my test so far
import Enzyme, {mount} from 'enzyme';
import Adapter from '#wojtekmaj/enzyme-adapter-react-17';
import Example from "./Example";
Enzyme.configure({adapter: new Adapter()});
describe('Example', () => {
test('searching requests the correct URL', () => {
fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([{a: 1}, {b: 2}]),
})
);
let searchButton;
const wrapper = mount(<Example/>);
const input = wrapper.find('input').at(0);
searchButton = wrapper.find('button').at(0);
input.simulate('change', {target: {value: 'Driver Name'}});
searchButton.simulate('click');
expect(searchButton.text()).toBe('Search');
expect(fetch.mock.calls.length).toBe(1);
expect(fetch.mock.calls[0][0]).toBe('/test?name=Driver Name');
});
});
I've tried wrapping act around various parts of the test, and it either still errors, or the name isn't appended to the query string. Can anyone point me in the right direction?
UPDATE:
Working test below for anyone (and probably me!) in the future
describe('Example', () => {
test('searching requests the correct URL', async () => {
fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([{a: 1}, {b: 2}]),
})
);
let searchButton: ReactWrapper;
const wrapper = mount(<Example/>);
const input = wrapper.find('input').at(0);
searchButton = wrapper.find('button').at(0);
input.simulate('change', {target: {value: 'Driver Name'}});
await act(async () => {
searchButton.simulate('click');
});
expect(searchButton.text()).toBe('Search');
expect(fetch.mock.calls.length).toBe(1);
expect(fetch.mock.calls[0][0]).toBe('/test?name=Driver Name');
});
});
I'm guessing that it is the clicking of the search button that is generating the act warning.
From react#16.9.0, act was changed to return a promise, meaning that you can avoid these types of warnings when testing async handlers.
Wrapping your search click simulation in an async act, might resolve the warning - but you might have to add a little timeout (not sure how this works with enzyme)
await act(() => {
searchButton.simulate('click');
})
Here are some more resources on the topic that might help you along the way:
secrets of the act(...) api
React’s sync and async act

React testing library: An update inside a test was not wrapped in act(...) & Can't perform a React state update on an unmounted component

In my test, the component receives its props and sets up the component.
This triggers a useEffect to make an http request (which I mock).
The fetched mocked resp data is returned, but the cleanup function inside the useEffect has already been called (hence the component has unmounted), so I get all these errors.
How do I prevent the component from un-mounting so that the state can be updated? I've tried act, no act, nothing causes the component to wait for the fetch to finish.
I should say my warning are just that, warnings, but I don't like all the red, and it indicates something is going wrong.
export const BalanceModule = (props) => {
const [report, setReport] = useState();
useEffect(() => {
fetch('http://.....').then((resp) => {
console.log("data returned!!!")
setReports((report) => {
return {...report, data: resp}
})
})
return () => {
console.log("unmounted!!!")
};
}, [report])
.... trigger update on report here
}
// the test:
test("simplified-version", async () => {
act(() => {
render(
<BalanceModule {...reportConfig}></BalanceModule>
);
});
await screen.findByText("2021-01-20T01:04:38");
expect(screen.getByText("2021-01-20T01:04:38")).toBeTruthy();
});
Try this:
test("simplified-version", async () => {
act(() => {
render(<BalanceModule {...reportConfig}></BalanceModule>);
});
await waitFor(() => {
screen.findByText("2021-01-20T01:04:38");
expect(screen.getByText("2021-01-20T01:04:38")).toBeTruthy();
});
});

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();
});

Where should I use act when testing an async react hook?

When testing an async react hook with #testing-library/react-hooks I see an error message. The error message mentions wrapping code in act(...) but I'm not sure where I should do this.
I have tried to wrap parts of the code in act(...) but each attempt leads to the test failing.
// day.js
import { useState, useEffect } from 'react';
import { getDay } from '../api/day';
export function useDay() {
const [state, set] = useState({ loading: false });
useEffect(() => {
let canSet = true;
set({ loading: true });
const setDay = async () => {
const day = await getDay();
if (canSet) {
set(day);
}
};
setDay();
return () => (canSet = false);
}, []);
return state;
}
// day.test.js
import { renderHook, act } from "#testing-library/react-hooks";
import { useDay } from "./day";
jest.mock("../api/day", () => ({
getDay: jest.fn().mockReturnValue({ some: "value" })
}));
describe.only("model/day", () => {
it("returns data", async () => {
const { result, waitForNextUpdate } = renderHook(() => useDay());
await waitForNextUpdate();
expect(result.current).toEqual({ some: "value" });
});
});
// test output
console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:102
Warning: An update to TestHook 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 is a known issue: https://github.com/testing-library/react-testing-library/issues/281
Before 16.9.0-alpha.0 React itself didn't handle the async stuff pretty good, so that has nothing to do with the testing library, really. Read the comments of the issue if you're interested in that.
You have two options now:
Update your React (& react-dom) to 16.9.0-alpha.0
Add a snippet (e. g. in your test setup file) to suppress that warning when console.log tries to print it:
// FIXME Remove when we upgrade to React >= 16.9
const originalConsoleError = console.error;
console.error = (...args) => {
if (/Warning.*not wrapped in act/.test(args[0])) {
return;
}
originalConsoleError(...args);
};

Resources