为什么 react 钩子在与 fetch api 一起使用时会抛出行为错误?

IT技术 reactjs react-hooks react-testing-library
2021-04-28 10:00:43

Warning: An update to App inside a test was not wrapped in act(...).每当我发出 API 请求并更新状态时,我都会不断进入我的测试套件。

我正在使用 react-testing-library。我也尝试使用 ReactDOM 测试工具,得到了相同的结果。我尝试的另一件事是将容器包裹在 中act,但仍然得到相同的结果。

请注意:我的应用程序有效并且我的测试通过。我只需要知道我做错了什么,或者是否是 react-dom 包中的错误导致该错误出现。模拟控制台错误并将其静音是不好的。

global.fetch = require('jest-fetch-mock');

it('should clear select content item', async () => {
    fetch.mockResponseOnce(JSON.stringify({ results: data }));

    const { container } = render(<App />);

    const content = container.querySelector('.content');

    await wait();

    expect(content.querySelectorAll('.content--item').length).toBe(2);
});

这是钩子的实现:

const [data, setData] = useState([]);
const [error, setError] = useState('');

const fetchInitData = async () => {
    try {
        const res = await fetch(API_URL);
        const data = await res.json();

        if (data.fault) {
            setError('Rate limit Exceeded');
        } else {
            setData(data.results);
        }
    } catch(e) {
        setError(e.message);
    }
};

useEffect(() => {
    fetchInitData();
}, [isEqual(data)]);
4个回答

这是一个已知问题,请在 Github https://github.com/kentcdodds/react-testing-library/issues/281 中查看此问题

对于那些像我一样在一年多之后偶然发现的人来说,Giorgio 提到的问题已得到解决,并wait已被替换为waitFor,如此处所述:

https://testing-library.com/docs/dom-testing-library/api-async/

既然如此,我相信现在对警告的解决方案应该是这样的:

import { render, waitFor } from '@testing-library/react';
// ...

it('should clear select content item', async () => {
    fetch.mockResponseOnce(JSON.stringify({ results: data }));

    const { container } = render(<App />);

    const content = container.querySelector('.content');

    await waitFor(() =>
        expect(content.querySelectorAll('.content--item').length).toBe(2);
    );
});

就我而言,我有一个App在一个异步组件加载数据useEffect挂钩,所以我得到的每一个测试此警告,使用beforeEach渲染App这是我的案例的具体解决方案:

  beforeEach(async () => {
    await waitFor(() => render(<App />));
  });

要消除act()警告,您需要确保您的Promise同步解决。您可以在此处阅读如何执行此操作。

概括:

对此的解决方案有点复杂:

  • 我们使用可以“立即”解决Promise的实现来全局填充 Promise,例如 promise
  • 使用自定义 babel 设置来转换您的 javascript,就像这个 repo 中的设置一样
  • 使用 jest.runAllTimers(); 这现在也将刷新Promise任务队列

我遇到了这个问题并放弃了使用等待和异步而是使用了 jest faketimers 等等,所以你的代码应该是这样的。

global.fetch = require('jest-fetch-mock');

it('should clear select content item', /*async */ () => {
    jest.useFakeTimers();
    fetch.mockResponseOnce(JSON.stringify({ results: data }));

    const { container } = render(<App />);

    const content = container.querySelector('.content');

    // await wait();
    act(() => {
      jest.runAllTimers();
    });

    expect(content.querySelectorAll('.content--item').length).toBe(2);
});