[英]Testing asynchronous useEffect
我的功能组件使用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());
});
});
除此之外,Jest 会发出警告: Warning: An update to App inside a test was not wrapped in act(...).
我想这有关系吗? 这怎么能解决?
好的,所以我想我已经想通了。 我现在正在使用最新的依赖项(enzyme 3.10.0、enzyme-adapter-react-16 1.15.1),我发现了一些惊人的东西。 Enzyme 的 mount() 函数似乎返回了一个 promise。 我在文档中没有看到任何关于它的内容,但是等待承诺解决似乎可以解决这个问题。 使用 react-dom/test-utils 中的行为也是必不可少的,因为它具有使行为起作用的所有新的 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());
});
继@ user2223059 的回答之后,您似乎也可以这样做:
// eslint-disable-next-line require-await
component = await mount(<MyComponent />);
component.update();
不幸的是,您需要 eslint-disable-next-line ,否则它会警告不必要的等待......但删除等待会导致不正确的行为。
我遇到了这个问题并遇到了这个线程。 我正在对钩子进行单元测试,但如果您的异步 useEffect 代码在组件中,则原理应该相同。
假设您有一个在挂载上执行一些异步工作的 react 钩子或组件,并且您想对其进行测试。 它可能看起来有点像这样:
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() 的帮助程序,它确保在您做出任何断言之前,与这些“单元”相关的所有更新都已被处理并应用于 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 钩子已经完成了它的工作。 如果我们需要更新钩子,那个动作也会被包裹在它自己的动作块中。
更复杂的测试用例可能如下所示:
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);
})
我也面临着类似的问题。 为了解决这个问题,我在酶测试中使用了 React 测试库的 waitFor function。
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);
}
});
该解决方案将等待我们的期望条件得到满足。 在期望条件内,您可以断言在 api 调用成功后呈现的任何 HTML 元素。
这是一个救生员
export const waitForComponentToPaint = async (wrapper: any) => {
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 0));
await wait(0);
wrapper.update();
});
};
测试中
await waitForComponentToPaint(wrapper);
经过大量实验,我想出了一个最终适合我的解决方案,希望能符合您的目的。
见下文
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
});
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.