测试useEffect内结合API调用的useState组件失败问题排查
React组件测试中setValue与setLoading未生效导致测试失败的解决方法
问题
测试React组件时,异步请求后的setValue和setLoading未触发,组件始终显示初始占位符数据和加载状态,导致测试断言失败。
组件代码
const [value, setValue] = useState({ street_name: '-', street_number: '-', city: '-', zip_code: '-', comment: '-', }); const [loading, setLoading] = useState(true); useEffect(() => { const fetchData = async () => { try { const response = await getAddressById(packageId); setValue(response); setLoading(false); } catch (error) { console.error(error); } }; fetchData(); }, []); return ( <> <Spinner show={loading} size="large" /> <TableData data-title="ID">{packageId}</TableData> <TableData data-title="Calle">{value.street_name}</TableData> <TableData data-title="Número">{value.street_number}</TableData> <TableData data-title="Ciudad">{value.city}</TableData> <TableData data-title="CP">{value.zip_code}</TableData> <TableData data-title="Referencia">{value.comment}</TableData> </> );
原测试代码
it('should render the component', async () => { jest.mock("../../../../../../../services", () => ({ getAddressById: jest.fn(() => Promise.resolve({ street_name: 'street_name', street_number: 'street_number', city: 'city', zip_code: 'zip_code', comment: 'comment', })), })); const props = { packageId: '11111', }; await act(async () => { const { container } = await render(<Item {...props} />); expect(screen.getByText(props.packageId)).toBeInTheDocument(); expect(screen.getByText('street_name')).toBeInTheDocument(); }); });
问题根源
- jest.mock放置位置错误:
jest.mock必须放在测试文件顶部(组件导入语句之前),因为jest在模块加载阶段替换mock。放在it块内部时,组件已导入原始的getAddressById方法,导致mock不生效,异步请求未按预期返回数据,setValue和setLoading无法触发。 - useEffect依赖项缺失:
useEffect依赖数组为空,但内部使用了packageIdprops,可能导致props变化时无法重新获取数据,也会触发ESLint警告影响稳定性。 - 断言时机不对:直接在
act内部断言异步渲染的元素,可能在状态更新完成前执行,导致找不到目标元素。
修复方案
1. 调整jest.mock位置
将jest.mock移到测试文件最顶部,确保组件导入前完成mock:
// 放在文件最顶部,组件导入之前 jest.mock("../../../../../../../services", () => ({ getAddressById: jest.fn(() => Promise.resolve({ street_name: 'street_name', street_number: 'street_number', city: 'city', zip_code: 'zip_code', comment: 'comment', })), })); import { render, screen, waitFor } from '@testing-library/react'; import Item from './path-to-your-component'; it('should render the component', async () => { const props = { packageId: '11111', }; render(<Item {...props} />); // 等待异步数据渲染完成 await waitFor(() => { expect(screen.getByText(props.packageId)).toBeInTheDocument(); expect(screen.getByText('street_name')).toBeInTheDocument(); }); });
2. 修复useEffect依赖数组
添加packageId到依赖数组,确保props变化时重新触发数据获取:
useEffect(() => { const fetchData = async () => { try { const response = await getAddressById(packageId); setValue(response); setLoading(false); } catch (error) { console.error(error); } }; fetchData(); }, [packageId]); // 添加packageId到依赖数组
3. 使用waitFor等待异步操作
waitFor会自动包裹在act中,专门用于等待异步更新后的断言,比手动使用act更可靠。
额外验证
可以在测试中添加mock调用验证,确保服务被正确触发:
import { getAddressById } from "../../../../../../../services"; // ... 在测试用例中 await waitFor(() => { expect(getAddressById).toHaveBeenCalledWith('11111'); });
内容的提问来源于stack exchange,提问作者Luiz Miranda
相关产品推荐
相关产品推荐

