最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React Testing Library - not wrapped in act() error - Stack Overflow

programmeradmin1浏览0评论

I'm having trouble using react-testing-library to test a toggle ponent.

On click of an icon (wrapped in a button ponent), I am expecting text to go from 'verified' to 'unverified'. In addition, a function is called where there are state updates.

However, the click event doesn't seem to work, and I am getting the below error:

> jest "MyFile.spec.tsx"

 FAIL  src/my/path/__tests__/MyFile.spec.tsx
  ponent 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 
        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 code, I have a function like this:

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

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

My test looks similar to this:

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 first expect passes as the function calls are called once, however I have the act() error from above, and also there is a failure as it seems that the text does not toggle from verified to unverified.

I am aware that usually the act error is an issue of async/waiting for calls to happen, but I thought that findByText should wait, and it seems like there is another issue I'm not catching here. Any help on what to do to debug/improve this test?

I'm having trouble using react-testing-library to test a toggle ponent.

On click of an icon (wrapped in a button ponent), I am expecting text to go from 'verified' to 'unverified'. In addition, a function is called where there are state updates.

However, the click event doesn't seem to work, and I am getting the below error:

> jest "MyFile.spec.tsx"

 FAIL  src/my/path/__tests__/MyFile.spec.tsx
  ponent 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/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 code, I have a function like this:

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

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

My test looks similar to this:

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 first expect passes as the function calls are called once, however I have the act() error from above, and also there is a failure as it seems that the text does not toggle from verified to unverified.

I am aware that usually the act error is an issue of async/waiting for calls to happen, but I thought that findByText should wait, and it seems like there is another issue I'm not catching here. Any help on what to do to debug/improve this test?

Share Improve this question edited Jun 30, 2021 at 11:13 stonerose036 asked Jun 30, 2021 at 10:22 stonerose036stonerose036 3014 silver badges18 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 1

There are 3 async functions that are called here when you click on the Remove Approval button.

First, you are setting the loading state to true, so it will load then the async function (myFunctionCalls) is called, and finally, the loader will disappear after the loading state is set to false.

In order to solve it, we have to wait for the loading to appear first, then myFunctionCalls is called, and then later we have to wait for loading to disappear.

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 you do not have loading text then you can use act(() => jest.advanceTimersByTime(500)); for extending the time till 500ms. When the time reaches 500ms, the async function would have been resolved.

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


Try this:

     // [...]

     fireEvent.click(button);

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

     // End of test
        
发布评论

评论列表(0)

  1. 暂无评论