You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试含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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 00:15:00