I have several tests written with Jest and React Testing Library. They all mock fetch and use a userEvent.click call to fire a submit button which makes a fetch request. State gets updated in the component and I make my assertions. I am using a useEffect hook to populate a data array but it only runs on initial load given that I'm passing an empty dependency array to it. All of my tests currently pass. If I run them all together, I get a wrong act() error that stems from useEffect:
Warning: It looks like you're using the wrong act() around your test interactions.
Be sure to use the matching version of act() corresponding to your renderer:
// for react-dom:
import {act} from 'react-dom/test-utils';
// ...
act(() => ...);
// for react-test-renderer:
import TestRenderer from react-test-renderer';
const {act} = TestRenderer;
// ...
act(() => ...);
However, when I run just one of them alone, I don't get the warning. I can run any one of them on their own and I get no warning. I only get the warning when I run two or more tests together.
My tests are:
describe("CartDetail", () => {
test("Order is submitted when user clicks Place Order button.", async () => {
global.fetch = jest.fn().mockImplementationOnce(() =>
Promise.resolve({
status: 200,
})
);
renderComponent();
await act(async () => {
userEvent.click(await screen.findByRole("button", { name: "Place Order" }));
});
expect(screen.queryByText("Your meal order was successfully processed.")).toBeInTheDocument();
});
test("Error message is displayed to user when order fails with a 400.", async () => {
global.fetch = jest.fn().mockImplementationOnce(() =>
Promise.resolve({
status: 400,
})
);
renderComponent();
await act(async () => {
userEvent.click(await screen.findByRole("button", { name: "Place Order" }));
});
expect(screen.queryByText("Please confirm that you are ordering at least one of each meal you have in your cart.")).toBeInTheDocument();
userEvent.click(screen.getByLabelText("Close alert"));
});
test("Error message is displayed to user when API fails.", async () => {
global.fetch = jest.fn().mockRejectedValueOnce(() =>
Promise.reject({
status: 500,
})
);
renderComponent();
await act(async () => {
userEvent.click(await screen.findByRole("button", { name: "Place Order" }));
});
expect(screen.queryByText("Your order failed.")).toBeInTheDocument();
userEvent.click(screen.getByLabelText("Close alert"));
});
});
I've read that you don't have to wrap userEvent in act() because it already is under the hood. However, if I don't wrap it in act, my tests fail and throw:
Warning: An update to CartDetail 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 */
Even if I comment out my assertions, my tests pass (of course) but I still get the wrong act() warning. The problem is coming directly from:
await act(async () => {
userEvent.click(await screen.findByRole("button", { name: "Place Order" }));
});
I don't understand how the issue stems from useEffect when it executes on initial load and doesn't run ever again, including when the button is clicked via userEvent.click(). I've tried using waitFor() instead and that yields the same exact results. I've scoured the internet and nothing has gotten me closer than this. This GitHub thread mentions that it's a known issue but it's a bit old now so I don't know if it's still valid.
act
because react state ends up updated and it causes these same types of problems. Our solution is to not end a test with something like thisuserEvent.click(screen.getByLabelText("Close alert"));
but instead after that, we have a waitFor that waits for the dialog to be closed. – Brokaw