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

如何在Vitest中测试加载屏?含数据与图片加载场景

解决Vitest中测试加载屏(数据+图片加载完成后隐藏)的问题

原测试的核心问题

  1. 错误的定时器推进时机:在waitFor内部调用vi.advanceTimersByTime会打乱轮询逻辑,导致Promise无法正确resolve。
  2. 未Mock图片加载事件:组件需要等待所有图片加载完成才隐藏加载屏,但测试环境默认不会处理真实图片的异步加载,导致逻辑卡死。

修正后的测试方案

第一步:Mock全局Image对象(模拟图片加载)

测试环境中需要模拟图片的load事件,让组件认为图片已加载完成。在测试前添加全局Mock:

beforeEach(() => {
  // 模拟图片加载,设置src后立即触发onload
  global.Image = vi.fn(() => ({
    src: '',
    onload: null,
    onerror: null,
    set src(_) {
      // 用微任务触发onload,模拟真实异步加载逻辑
      queueMicrotask(() => this.onload?.());
    }
  }));
});

如果需要测试图片延迟加载的场景,可以把queueMicrotask换成带定时器的逻辑:

set src(_) {
  setTimeout(() => this.onload?.(), 200);
}

第二步:正确编写测试用例

import { render, screen, waitFor, flushPromises } from '@testing-library/react';
import App from './App';
import { fetchData } from '../../ShoppingCart-Core/api.js';
import { products } from './mock-products.js';

vi.mock('../../ShoppingCart-Core/api.js', () => ({
  fetchData: vi.fn(() => 
    new Promise(resolve => setTimeout(() => resolve(products), 500))
  )
}));

it('加载屏在数据和图片都加载完成后隐藏', async () => {
  vi.useFakeTimers();
  render(<App />);

  // 验证初始状态:加载屏显示
  expect(screen.getByText('加载中...')).toBeInTheDocument();

  // 推进定时器,让fetchData完成
  vi.advanceTimersByTime(500);
  // 刷新Promise,触发setProducts后的组件重渲染
  await flushPromises();

  // (可选)验证数据已渲染
  expect(screen.getAllByRole('img')).toHaveLength(products.length);

  // 等待图片加载完成的状态更新,验证加载屏隐藏
  await waitFor(() => {
    expect(screen.queryByText('加载中...')).not.toBeInTheDocument();
  });

  vi.useRealTimers();
});

第三步:组件内补充图片加载监听逻辑(如果还没实现)

要让测试逻辑成立,组件内需要监听图片加载状态,示例逻辑如下:

const [products, setProducts] = useState([]);
const [imagesLoaded, setImagesLoaded] = useState(false);

useEffect(() => {
  const getProducts = async () => {
    const res = await fetchData();
    setProducts(res);
  };
  getProducts();
}, []);

useEffect(() => {
  if (products.length === 0) return;

  let loadedCount = 0;
  const totalImages = products.length;

  const markImageLoaded = () => {
    loadedCount++;
    if (loadedCount === totalImages) setImagesLoaded(true);
  };

  // 为每个图片绑定加载事件
  products.forEach(item => {
    const img = new Image();
    img.src = item.imageUrl;
    img.onload = markImageLoaded;
    img.onerror = markImageLoaded; // 处理加载失败,避免加载屏一直显示
  });

  // 清理函数
  return () => {
    products.forEach(item => {
      const img = new Image();
      img.onload = null;
      img.onerror = null;
    });
  };
}, [products]);

// 加载屏显示条件:数据未加载 或 图片未加载完成
const isLoading = products.length === 0 || !imagesLoaded;

关键说明

  • flushPromises的作用:推进定时器后,fetchData的Promise会resolve,但setProducts是异步状态更新,需要flushPromises等待组件完成重渲染。
  • 分阶段验证:先验证初始加载屏显示,再验证数据加载后的状态,最后验证图片加载完成后加载屏隐藏,确保每一步逻辑都符合预期。

内容的提问来源于stack exchange,提问作者vatsal chauhan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 04:12:04