如何测试含Axios API调用的React组件?测试失败排查
React组件测试失败:图片始终显示加载占位图
应用运行正常,但测试持续失败,错误信息:
期望元素的src属性为"https://via.placeholder.com/300x300.png?text=robin",但实际收到的是"https://via.placeholder.com/200x300/86efac?text=Loading..."
怀疑是Axios请求Mock不正确或懒加载逻辑问题,相关代码如下:
测试代码
import { screen, waitFor } from "@testing-library/react"; import { renderWithProviders } from "../utils/utils-for-tests"; import axios from "axios"; import BirdImg from "../components/BirdImg"; class IntersectionObserver { observe() { return null; } disconnect() { return null; } } window.IntersectionObserver = IntersectionObserver; jest.mock("axios", () => ({ get: jest.fn(), })); const mockLoadedResponse = { data: { results: [ { urls: { thumb: "https://via.placeholder.com/300x300.png?text=robin", }, }, ], }, }; test("shows the image", async () => { axios.get.mockResolvedValue(mockLoadedResponse); renderWithProviders(<BirdImg name="robin" />); const birdImage = await screen.findByRole("img"); await waitFor(() => { expect(birdImage).toBeInTheDocument(); expect(birdImage).toHaveAttribute( "src", "https://via.placeholder.com/300x300.png?text=robin" ); }); });
待测试组件代码
import { useEffect, useRef, useState } from "react"; import { fetchBirdImg } from "../api/unsplash"; function BirdImg({ name }) { const imgRef = useRef(null); const [loaded, setLoaded] = useState(false); const [error, setError] = useState(false); useEffect(() => { const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { fetchBirdImg(name).then((data) => { imgRef.current.src = data[0].urls.thumb; setLoaded(true); }); observer.unobserve(imgRef.current); } }); }); observer.observe(imgRef.current); return () => observer.disconnect(); }, [name]); let imgSrc; if (loaded === false) { imgSrc = `https://via.placeholder.com/200x300/86efac?text=Loading...`; } if (loaded) { imgSrc = imgRef.current.src; } if (error === true) { imgSrc = `https://via.placeholder.com/200x300/86efac?text=Error`; } return ( <img ref={imgRef} onLoad={() => { setLoaded(true); }} onError={() => { setError(true); }} src={imgSrc} alt={name} className="w-20" /> ); } export default BirdImg;
API调用代码
import axios from "axios"; async function fetchBirdImg(name) { const response = await axios.get( "https://api.unsplash.com/search/photos?per_page=1&orientation=portrait", { headers: { Authorization: "auth key", }, params: { query: `${name}` }, } ); return response.data.results; } export { fetchBirdImg };
解决方案
1. 修复IntersectionObserver Mock,触发懒加载逻辑
当前Mock没有执行交叉观察的回调,导致不会触发图片请求。修改Mock类,在observe时立即调用回调并标记元素为已进入视口:
class IntersectionObserver { constructor(callback) { this.callback = callback; } observe(element) { // 模拟元素进入视口,触发回调 this.callback([{ isIntersecting: true, target: element }]); } disconnect() {} unobserve() {} } window.IntersectionObserver = IntersectionObserver;
2. 修正组件状态与图片src的同步逻辑
组件中直接修改DOM的src后更新loaded状态,存在状态与DOM不同步的竞态问题。改用state存储图片地址,避免直接操作DOM:
import { useEffect, useRef, useState } from "react"; import { fetchBirdImg } from "../api/unsplash"; function BirdImg({ name }) { const imgRef = useRef(null); const [loaded, setLoaded] = useState(false); const [error, setError] = useState(false); // 用state存储图片地址 const [imgSrc, setImgSrc] = useState(`https://via.placeholder.com/200x300/86efac?text=Loading...`); useEffect(() => { const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { fetchBirdImg(name) .then((data) => { setImgSrc(data[0].urls.thumb); }) .catch(() => setError(true)); observer.unobserve(imgRef.current); } }); }); observer.observe(imgRef.current); return () => observer.disconnect(); }, [name]); return ( <img ref={imgRef} onLoad={() => setLoaded(true)} onError={() => setError(true)} src={error ? `https://via.placeholder.com/200x300/86efac?text=Error` : imgSrc} alt={name} className="w-20" /> ); } export default BirdImg;
3. 优化测试逻辑,简化等待断言
直接使用findByRole的选项等待目标图片加载完成,无需额外嵌套waitFor:
test("shows the image", async () => { axios.get.mockResolvedValue(mockLoadedResponse); renderWithProviders(<BirdImg name="robin" />); // 等待图片src变为目标地址 const birdImage = await screen.findByRole("img", { src: "https://via.placeholder.com/300x300.png?text=robin" }); expect(birdImage).toBeInTheDocument(); });
内容的提问来源于stack exchange,提问作者user20257043
相关产品推荐
相关产品推荐

