React组件useEffect结合fetch的异步代码测试方法及问题排查
React+TS 应用中带fetch的异步逻辑测试排障指南
第一类报错原因:fetch Mock 结构不匹配
- 报错“无法读取返回值的then属性”的核心原因:初始编写全局Mock时,仅将fetch设置为无返回值的jest模拟函数,调用后得到undefined,自然不存在then方法。
- 错误写法示例:
global.fetch = jest.fn()// 调用返回undefined,访问.then直接触发类型/运行时错误 - 正确Mock实现:必须完全匹配真实fetch的两层异步返回结构,也就是:
- fetch方法返回一个Promise
- Promise resolve的结果中包含可调用的json()方法
- json()方法本身也返回一个Promise,resolve最终的接口数据
示例代码:
// 测试文件顶层初始化全局Mock global.fetch = jest.fn().mockResolvedValue({ json: () => Promise.resolve([ { id: 1, name: "Bohemian Rhapsody", releaseYear: 1975 }, { id: 2, name: "Don't Stop Me Now", releaseYear: 1979 } ]) })
第二类问题:测试流程未等待异步队列执行完成
- 测试通过但debug始终显示加载态的核心原因:测试执行是同步流程,render方法执行后会立刻返回初始渲染的DOM,此时组件内部的fetch请求、json解析、setState触发的重渲染都还在事件循环的异步队列中没有执行。此前将useEffect内的请求逻辑改写为async/await立即执行函数后测试可以通过,本质是测试的断言只校验了初始加载态的存在,并未等待后续异步流程跑完;此时如果在render之后立刻调用
screen.debug(),拿到的永远是初始加载态的DOM,即使后续异步逻辑跑完,测试流程已经结束,自然看不到最终的渲染结果。 - 错误测试流程示例:
render(<SongList />) // 立刻debug,此时异步逻辑尚未执行,DOM为加载态 screen.debug() expect(screen.getByText("加载中...")).toBeInTheDocument() - 正确的异步等待实现:
直接使用@testing-library提供的*findBy*系列异步查询API*,该API内置了自动轮询机制,会等待所有异步任务执行完成、目标元素出现在DOM中后再返回结果,不需要手动编写等待逻辑。
正确的组件测试代码示例:import { render, screen } from '@testing-library/react' import '@testing-library/jest-dom' import SongList from './SongList' test('歌曲列表加载完成后正确渲染内容', async () => { render(<SongList />) // findBy* 自动等待目标元素渲染完成 const songTitle = await screen.findByText("Bohemian Rhapsody") // 此时DOM已经是异步请求完成后的最终状态,debug可输出完整列表 screen.debug() // 断言内容 expect(songTitle).toBeInTheDocument() expect(screen.queryByText("加载中...")).not.toBeInTheDocument() })
自定义Hook测试注意事项
如果是单独测试自定义Hook,需要使用renderHook方法,并搭配waitFor工具等待异步状态更新:
import { renderHook, waitFor } from '@testing-library/react' import { useQueenSongs } from './useQueenSongs' test('自定义Hook正确返回拉取的歌曲数据', async () => { const { result } = renderHook(() => useQueenSongs()) // 初始状态校验 expect(result.current.loading).toBe(true) // 等待异步流程完成、状态更新 await waitFor(() => { expect(result.current.loading).toBe(false) }) // 校验最终状态 expect(result.current.songs).toHaveLength(2) })
内容的提问来源于stack exchange,提问作者brff19
相关产品推荐
相关产品推荐

