React Testing Library 中如何正确测试异步加载的数据?

一育柯 阅读 60

我用 React Testing Library 测试一个组件,它在 useEffect 里通过 fetch 获取数据并更新状态。但测试时总是拿不到渲染后的数据,断言失败。是不是要加 await 或者用 waitFor?

这是我的组件代码:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(<code>/api/users/${userId}</code>)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]);

  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

测试里我 mock 了 fetch,但 screen.getByText 始终找不到 user.name,控制台显示还是 “Loading…”。该怎么写才对?

我来解答 赞 11 收藏
二维码
手机扫码查看
2 条解答
❤柯依
❤柯依 Lv1
啊,异步测试这个问题我踩过不少坑,我来详细说说怎么解决。原理是这样:React Testing Library 需要等待异步操作完成才能断言,不然组件可能还停在 loading 状态。

首先你得做这几步:

1. 确保正确 mock 了 fetch。要用 jest.spyOn 或者全局 mock,建议用 jest.spyOn 更灵活
2. 测试中必须等待组件完成更新。可以用 waitFor 配合 findBy 查询
3. 别忘了 cleanup 和正确设置测试环境

来看具体代码实现。先 mock fetch:

// 测试文件顶部设置 mock
beforeEach(() => {
jest.spyOn(global, 'fetch').mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve({ name: '张三' }),
})
);
});

afterEach(() => {
jest.restoreAllMocks();
});


然后是测试用例写法。关键是用 async/await 和 waitFor:

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

test('应该显示用户姓名', async () => {
render(<UserProfile userId="123" />);

// 初始应该是 loading
expect(screen.getByText('Loading...')).toBeInTheDocument();

// 等待异步操作完成
await waitFor(() => {
// 用 findByText 因为它本身是异步的
expect(screen.getByText('张三')).toBeInTheDocument();
});

// 或者更简洁的写法:
// expect(await screen.findByText('张三')).toBeInTheDocument();
});


这里有几个要点:
- getByText 是同步的,找不到会直接报错
- findByText 是异步的,会不断重试直到超时
- waitFor 可以包裹任何断言,内部会重试

有时候你可能还需要 mock 更复杂的 fetch 场景,比如错误情况:

test('应该处理请求错误', async () => {
jest.spyOn(global, 'fetch').mockRejectedValue(new Error('网络错误'));

render(<UserProfile userId="123" />);

// 假设你的组件有错误处理
await waitFor(() => {
expect(screen.getByText('加载失败')).toBeInTheDocument();
});
});


如果测试还是很慢,可以调整 waitFor 的超时时间:

await waitFor(() => {
expect(screen.getByText('张三')).toBeInTheDocument();
}, { timeout: 5000 }); // 默认是1秒


最后提醒下,如果你用 React 18,可能需要额外注意 concurrent rendering 的影响,但基本思路是一样的。测试异步组件就是三个关键点:正确 mock、适当等待、合理断言。
点赞 4
2026-03-08 09:04
萌新.艳丽
这种异步渲染的测试确实容易踩坑,我来给你个靠谱的解法。问题出在你的测试没有等数据加载完成就断言了。

首先确保你的测试文件顶部引入了这些:
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';


然后测试代码要这么写:
test('should display user name after loading', async () => {
// mock服务端返回
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ name: '老王' }),
})
);

render();

// 先验证loading状态
expect(screen.getByText('Loading...')).toBeInTheDocument();

// 关键在这里:等异步渲染完成
await waitFor(() => {
expect(screen.getByText('老王')).toBeInTheDocument();
});
});


几个关键点:
1. 测试函数要用async声明
2. 用waitFor包裹最终的断言,它会自动重试直到超时
3. 别忘了await waitFor

我经常遇到这种问题,特别是刚用RTL的时候。有时候还会配合findByText使用,但waitFor更通用些。如果还不行,可能是你的mock没设置对,检查下fetch的mock实现。
点赞 1
2026-03-05 13:03