React Testing Library 中如何正确模拟异步数据加载?
我在用 React Testing Library 测试一个组件,它会在 useEffect 里调用 API 获取数据。我用 jest.mock 模拟了请求,但测试总是报错说找不到加载后的元素,是不是没等异步完成?
我试过用 await waitFor(() => expect(...).toBeInTheDocument()),但有时候还是失败,控制台还提示 “act” 相关的警告。下面是我的测试代码:
test('显示用户信息', async () => {
render(<UserProfile userId="123" />);
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
});
API mock 是这样写的:
jest.mock('../api', () => ({
fetchUser: () => Promise.resolve({ name: 'John Doe' })
}));
到底该怎么写才能稳定通过测试啊?
两个关键改动:
第一,把
getByText改成findByText,它本身就是异步的,会自动等待元素出现:第二,让你的 mock 带一点延迟,模拟真实 API 的行为:
这样写的好处是给 React 足够的时间处理状态更新,act 警告自然就没了。
如果你的 API 在组件里是这样调用的:
那上面两种改法足够让测试稳定通过。
findByText默认会等 1000ms,足够cover大多数场景。问题出在两个地方:
第一个,mock 需要返回真正的 Promise
用 jest.fn() 包装一下,这样能确保 mock 被调用时返回的是真正的 Promise。
第二个,推荐用 findBy 替代 waitFor + getBy
findBy 本身就是异步的,会自动等待,比你手动写 waitFor 稳定:
findBy 内部已经帮你做了 waitFor 的事情,而且默认超时时间是 1000ms,足够大多数场景用。
如果还是有问题,加个 assert 进去看看到底返回了什么
act 警告通常是因为组件渲染时触发的状态更新没有被正确等待,findBy 能解决大部分这种情况。如果你的 UserProfile 组件在加载状态会先显示 loading 文字,可以先 assert loading 消失,再 assert 数据出现,这样更符合实际用户场景。