reactjs 测试异步使用效果

jckbn6z7  于 2023-02-15  发布在  React
关注(0)|答案(6)|浏览(122)

我的功能组件使用useEffect钩子在挂载时从API获取数据。我希望能够测试获取的数据是否正确显示。
虽然这在浏览器中运行良好,但测试失败,因为钩子是异步的,组件没有及时更新。
实时代码:https://codesandbox.io/s/peaceful-knuth-q24ih?fontsize=14

    • 应用程序js**
import React from "react";

function getData() {
  return new Promise(resolve => {
    setTimeout(() => resolve(4), 400);
  });
}

function App() {
  const [members, setMembers] = React.useState(0);

  React.useEffect(() => {
    async function fetch() {
      const response = await getData();

      setMembers(response);
    }

    fetch();
  }, []);

  return <div>{members} members</div>;
}

export default App;
    • 应用程序测试js**
import App from "./App";
import React from "react";
import { mount } from "enzyme";

describe("app", () => {
  it("should render", () => {
    const wrapper = mount(<App />);

    console.log(wrapper.debug());
  });
});

除此之外,杰斯特还发出警告说:Warning: An update to App inside a test was not wrapped in act(...).
我想这是有关系的吧?这怎么能修好呢?

b4lqfgs4

b4lqfgs41#

好了,我想我已经弄明白了一些事情。我现在正在使用最新的依赖项(enzyme 3.10.0,enzyme-adapter-react-16 1.15.1),我发现了一些惊人的东西。()函数似乎返回了一个承诺。我在文档中没有看到任何关于它的内容,但是等待这个承诺的解决似乎可以解决这个问题。使用react-dom/test-utils中的act也是必不可少的,因为它拥有所有新的React魔法来使行为工作。

it('handles async useEffect', async () => {
    const component = mount(<MyComponent />);
    await act(async () => {
        await Promise.resolve(component);
        await new Promise(resolve => setImmediate(resolve));
        component.update();
    });
    console.log(component.debug());
});
30byixjq

30byixjq2#

根据@user2223059的回答,看起来您也可以执行以下操作:

// eslint-disable-next-line require-await     
component = await mount(<MyComponent />);
component.update();

不幸的是,您需要eslint-disable-next-line,因为否则它会警告不必要的等待......但删除等待会导致不正确的行为。

mtb9vblg

mtb9vblg3#

我遇到了这个问题,遇到了这个线程。我正在单元测试一个钩子,但是如果你的async useEffect代码在一个组件中,原则应该是相同的。因为我正在测试一个钩子,我从react hooks testing library调用renderHook。如果你正在测试一个常规组件,你应该从react-dom调用render,根据文档。

问题是

假设你有一个react钩子或组件在mount上做一些异步工作,你想测试它,它可能看起来像这样:

const useMyExampleHook = id => {
    const [someState, setSomeState] = useState({});
    useEffect(() => {
        const asyncOperation = async () => {
            const result = await axios({
                url: `https://myapi.com/${id}`,
                method: "GET"
            });
            setSomeState(() => result.data);
        }

        asyncOperation();

    }, [id])
    return { someState }
}

到目前为止,我一直在像这样对这些钩子进行单元测试:

it("should call an api", async () => {
        const data = {wibble: "wobble"};
        axios.mockImplementationOnce(() => Promise.resolve({ data}));

        const { result } = renderHook(() => useMyExampleHook());
        await new Promise(setImmediate);

        expect(result.current.someState).toMatchObject(data);
});

并且使用await new Promise(setImmediate);来"刷新"承诺。这对于像我上面的一个这样的简单测试来说工作正常,但是当我们在一个测试中开始对钩子/组件进行多次更新时,似乎会在测试呈现器中引起某种竞争条件。

答案是

答案是正确使用act()
在编写[单元测试]时...... react-dom/test-utils提供了一个名为act()的帮助器,它确保在您做出任何Assert之前,与这些"单元"相关的所有更新都已经被处理并应用到DOM。
因此,我们的简单测试代码实际上是这样的:

it("should call an api on render and store the result", async () => {
        const data = { wibble: "wobble" };
        axios.mockImplementationOnce(() => Promise.resolve({ data }));

        let renderResult = {};
        await act(async () => {
            renderResult = renderHook(() => useMyExampleHook());
        })

        expect(renderResult.result.current.someState).toMatchObject(data);
    });

最重要的区别是异步操作是围绕钩子的初始呈现进行的。这确保了useEffect钩子在我们开始检查状态之前已经完成了它的工作。如果我们需要更新钩子,那个操作也会被 Package 在它自己的act块中。
更复杂的测试用例可能如下所示:

it('should do a new call when id changes', async () => {
        const data1 = { wibble: "wobble" };
        const data2 = { wibble: "warble" };

        axios.mockImplementationOnce(() => Promise.resolve({ data: data1 }))
        .mockImplementationOnce(() => Promise.resolve({ data: data2 }));

        let renderResult = {};
        await act(async () => {
            renderResult = renderHook((id) => useMyExampleHook(id), {
                initialProps: { id: "id1" }
            });
        })

        expect(renderResult.result.current.someState).toMatchObject(data1);

        await act(async () => {
            renderResult.rerender({ id: "id2" })
        })

        expect(renderResult.result.current.someState).toMatchObject(data2);
    })
2q5ifsrm

2q5ifsrm4#

我也面临着类似的问题。为了解决这个问题,我在酶测试中使用了React测试库的waitFor函数。

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

it('render component', async () => {

    const wrapper = mount(<Component {...props} />);
    await waitFor(() => {
       wrapper.update();
       expect(wrapper.find('.some-class')).toHaveLength(1);
    }
});

此解决方案将等待我们的expect条件满足。在expect条件中,您可以Assert任何在API调用成功后呈现的HTML元素。

u5rb5r59

u5rb5r595#

这是个救命稻草

export const waitForComponentToPaint = async (wrapper: any) => {
  await act(async () => {
    await new Promise(resolve => setTimeout(resolve, 0));
    await wait(0);
    wrapper.update();
  });
};

试验中

await waitForComponentToPaint(wrapper);
uqzxnwby

uqzxnwby6#

经过大量的实验,我已经想出了一个解决方案,终于为我工作,并希望将符合您的目的。
见下文

import React from "react";
import { mount } from "enzyme";
import { act } from 'react-dom/test-utils';
import App from "./App";

describe("app", () => {
  it("should render", async () => {
    const wrapper = mount(<App />);

    await new Promise((resolve) => setImmediate(resolve));
    await act(
      () =>
        new Promise((resolve) => {
          resolve();
        })
    );
    wrapper.update();
    // data loaded
  });
});

相关问题