Error message: Act must be used when rendering components with React Testing Library

I am facing difficulty while using react-testing-library to test a toggle component.

Upon clicking an icon (which is wrapped in a button component), I expect the text to switch from 'verified' to 'unverified'. Additionally, a function is triggered which involves state updates.

However, the click event does not seem to trigger correctly, resulting in the following error:

> jest "MyFile.spec.tsx"

 FAIL  src/my/path/__tests__/MyFile.spec.tsx
  component MyFile
    ✓ renders when opened (94 ms)
    ✓ renders with items (33 ms)
    ✕ toggles verification status on click of icon button (100 ms)


  console.error
    Warning: An update to MyFile 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 MyFile (/path/to/myfile.tsx:44:3)
        at ThemeProvider (/users/node_modules/@material-ui/styles/ThemeProvider/ThemeProvider.js:48:24)

      123 |       );
      124 |     } finally {
    > 125 |       setIsLoading(false);
          |       ^
      126 |     }
      127 |   };
      128 |

      at printWarning (node_modules/react-dom/cjs/react-dom.development.js:67:30)
      at error (node_modules/react-dom/cjs/react-dom.development.js:43:5)
      at warnIfNotCurrentlyActingUpdatesInDEV (node_modules/react-dom/cjs/react-dom.development.js:24064:9)
      at dispatchAction (node_modules/react-dom/cjs/react-dom.development.js:16135:9)
      at handleConfirm (src/modules/myfile.tsx:125:7)

In my codebase, there exists a function defined as follows:

const handleSubmit = async() => {
  if(isLoading) {
    return;
  }

  try {
    setIsLoading(true);
    await myFunctionCalls();
  } catch (error){
    console.log(error)
  } finally {
    setIsLoading(false)
  }
};

A snippet of my test script is as shown below:

test('toggles verification status on click of icon button', async () => {
    renderWithTheme(
    <MyComponent/>,
   );

  const updateVerificationMock = jest.fn();
  const callFunctionWithSerializedPayloadMock =
    callFunctionWithSerializedPayload as jest.Mock;
  callFunctionWithSerializedPayloadMock.mockImplementation(
    () => updateVerificationMock,
  );

    const button = screen.getByRole('button', {name: 'Remove approval'});
    fireEvent.click(button);

    await act(async() => {
      expect(myFunctionCalls).toHaveBeenCalledTimes(1);
    });
    expect(await screen.findByText('unverified')).toBeInTheDocument();
  });

The initial expectation passes as the function calls are executed once. However, I receive the act() error mentioned above and the text switching from verified to unverified does not occur, leading to test failure. I understand that the act error usually pertains to awaiting for asynchronous operations, but I assumed findByText handles this. It appears there might be another underlying issue that I need help pinpointing and rectifying. Any guidance on debugging/improving this test would be greatly appreciated.

Answer №1

When the Remove Approval button is clicked, three asynchronous functions are triggered in succession.

Initially, the loading state is set to true, indicating that content is being loaded. Then, the async function (myFunctionCalls) is executed, followed by the disappearance of the loader once the loading state returns to false.

To address this sequence, it is essential to first acknowledge the presence of the loading state, proceed with the execution of myFunctionCalls, and eventually confirm the removal of the loading indicator.

test("toggles verification status on click of icon button", async () => {
  renderWithTheme(<MyComponent />);

  const updateVerificationMock = jest.fn();
  const callFunctionWithSerializedPayloadMock =
    callFunctionWithSerializedPayload as jest.Mock;
  callFunctionWithSerializedPayloadMock.mockImplementation(
    () => updateVerificationMock
  );

  const button = screen.getByRole("button", { name: "Remove approval" });
  fireEvent.click(button);

  expect(await screen.findByText(/loading/i)).toBeInTheDocument();

  await waitFor(() => {
    expect(myFunctionCalls).toHaveBeenCalledTimes(1);
  });

  await waitForTheElementToBeRemoved(() => {
    expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
  });


  expect(await screen.findByText("unverified")).toBeInTheDocument();
});

If there is no loading text present, utilize

act(() => jest.advanceTimersByTime(500));
to extend the duration to 500ms. This ensures adequate time for the async function resolution upon reaching the specified timeframe.

beforeEach(() => {
  jest.useFakeTimers();
})

afterEach(() => {
  jest.runAllPendingTimers();
  jest.useRealTimers()
})

test("toggles verification status on click of icon button", async () => {
  renderWithTheme(<MyComponent />);

  const updateVerificationMock = jest.fn();
  const callFunctionWithSerializedPayloadMock =
    callFunctionWithSerializedPayload as jest.Mock;
  callFunctionWithSerializedPayloadMock.mockImplementation(
    () => updateVerificationMock
  );

  const button = screen.getByRole("button", { name: "Remove approval" });
  fireEvent.click(button);

  act(() => jest.advanceTimersByTime(500));

  await waitFor(() => {
    expect(myFunctionCalls).toHaveBeenCalledTimes(1);
  });

  act(() => jest.advanceTimersByTime(500));

  expect(await screen.findByText("unverified")).toBeInTheDocument();
});


Answer №2

Give this a shot:

     // [...]

     fireEvent.press(button);

     await waitFor(() => {
          expect(myFunctionCalls).toHaveBeenCalledTimes(1),
          expect(screen.findByText('unverified')).toBeInTheDocument()
        });

     // End of test
        

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating HTML elements using Vue.js

Currently, I am facing an issue while attempting to render a template using the push mutation method. My goal is to push a section component, but instead of seeing the desired template content on my page, I am only getting the raw output of <vsection> ...

Utilizing the default event object in ag-Grid's event methods with JavaScript

I am a newcomer to ag-grid and I need help with calling event.preventDefault() in the "cellEditingStopped" grid event. Unfortunately, I am struggling to pass the default JavaScript event object into it. Is there a way to make this work? Additionally, I al ...

showing the values stored in local storage

My goal is to show only the values stored in local storage, excluding the key value that displays all data after submitting the login form. welcome <span id="demo"></span>; <script> document.getElementById('demo').innerHTML = ...

Refresh the HTML content within a specified div element

In my index.html, there is a graph (created using d3.js) along with some code that displays a stepper with a number of steps equal to the child nodes of the clicked node: <div ng-include="ctrl.numlab==2 && 'views/stepper-two-labs.htm ...

Error encountered with TypeScript compiler when using a React Stateless Function component

I am attempting to create a React Stateless Function component using TypeScript. Take a look at the code snippet below: import * as React from 'react'; import {observer} from 'mobx-react'; export interface LinkProps { view: any; ...

Assign the private members of the class to the arguments of the constructor

class Bar { #one #two #three #four #five #six #seven #eight #nine #ten #eleven #twelve #thirteen #fourteen #fifteen #sixteen constructor( one, two, three, four, five, six, seven, eight, ...

Tips for transforming a scroll element into the viewport using Angular 2+

This is a sample Here is a component with a list of items: class HomeComponent { text = 'foo'; testObject = {fieldFirst:'foo'}; itemList = [ '1', '2', '3', & ...

How to drop several pins on Google Maps with JavaScript

I am working on incorporating multiple markers into a Google map using ajax, javascript, and php. Although there are no errors in my code, the markers are not appearing as expected. I would greatly appreciate any assistance with this issue. Please refer to ...

When the only source is available, the image undergoes a transformation

Is there a way to dynamically adjust the height of an image using JQuery or JavaScript, but only when the image source is not empty? Currently, I have an image element with a fixed height, and even when there is no source for it, Chrome still reserves sp ...

Validating numbers in React JS input fields component

I need assistance with implementing number validation for 3 Textfields in my application. Currently, the code displays an error message if a Textfield is empty, but I am stuck on validating whether the input is text or numbers. import React from 'rea ...

Executing Basic Calculations Instantly with Live Data in Qualtrics

I am encountering an issue with displaying the values on a slider in Qualtrics. I need to show the value where the respondent has placed the handle, as well as the complementary value from the other end of the scale. For example, if the user has chosen 55, ...

What are the best practices for implementing responsiveness in Material-UI with React?

I am new to this framework and have been using react-bootstrap so far. As far as I know, Material-UI does not come with a GRID system, so I integrated react-flexbox-grid into my application. However, the components from Material-UI do not seem to align co ...

Is there a way to achieve a transparent background while animating the second text?

I am seeking to create a unique typography animation that involves animating the second text, which is colored and consists of multiple text elements to animate. The animation should showcase each text element appearing and disappearing one after the other ...

Experiment with the Users.Get function available in vk-io

I am having an issue with a create command: Ban [@durov] and I encountered an error. Here is how I attempted to solve the problem: let uid = `${message.$match[1]}` let rrr = uid.includes('@') if(rrr == true){ let realid = uid.replace(/[@]/g, &ap ...

Tips for creating a typescript typeguard function for function types

export const isFunction = (obj: unknown): obj is Function => obj instanceof Function; export const isString = (obj: unknown): obj is string => Object.prototype.toString.call(obj) === "[object String]"; I need to create an isFunction method ...

Achieving JSON element sorting in the most effective way

https://i.stack.imgur.com/NQbdN.png Each array contains the following information: {{ id: 39, treaty_number: "qwe", insurant_name: "222", belonging_to_the_holding_company: "test", date_start: "2016-04-15", etc }} Is there a way to sort each array in asc ...

After a period of 10 minutes with no activity, proceed to the next page

I am in the process of developing a custom local website that will be displayed on a large touch screen at my current workplace. Only one user can interact with it at a time. My client has requested a screensaver feature to appear after 10 minutes of no i ...

Eliminate the blank choice from X-editable Select

Lately, I've been dealing with AngularJS and X-editable and one thing that has been troubling me is the fact that the select option always includes an empty first child. Here's the HTML <span editable-select="curuser.roles" e-name="roles" ...

Testing Vuex getters with unit tests in Vue

In my component, I have a getter called defaultInternalAccountId that I am watching for changes. Whenever this property changes, the method fetchAccount is called. This method is also triggered when the component is mounted. My goal is to write a test that ...

When assigning JSON to a class object, the local functions within the class became damaged

This is a demonstration of Object Oriented Programming in JavaScript where we have a parent Class called Book with a child class named PriceDetails. export class Book { name: String; author: String; series: String; priceDetails: Array<Price> ...