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

使用Suspense与MSW测试加载状态时,骨架屏组件无法定位的问题排查

问题分析与解决方案

方案正确性判断

方案2(通过MSW拦截请求并添加延迟)是正确的测试方案。方案1直接测试React Query的hook状态,属于hook单元测试范畴,而我们要验证的是组件在加载状态下的UI表现,应该从组件整体渲染的角度出发,模拟真实的请求延迟来触发加载状态,这才是符合需求的集成测试思路。

骨架屏无法定位的常见原因及修复方法

1. MSW延迟设置错误

不少人会用ctx.delay(100)实现延迟,但部分MSW版本存在兼容性问题,导致延迟不生效。建议手动用Promise+setTimeout实现:

server.use(
  rest.get('/api/categorized-recipes', (req, res, ctx) => {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(res(ctx.json(mockData)));
      }, 100); // 设置100ms足够触发加载状态
    });
  })
);

2. React Query缓存跳过加载状态

React Query默认会缓存请求结果,如果测试前没有重置Query Client,后续测试会直接读取缓存,不会进入加载状态。在beforeEach里重置Query Client:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

let queryClient: QueryClient;

beforeEach(() => {
  queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        cacheTime: 0, // 禁用缓存
        retry: false, // 禁用重试避免干扰测试
      },
    },
  });
});

// 渲染组件时必须包裹QueryClientProvider
render(
  <QueryClientProvider client={queryClient}>
    <MainContent />
  </QueryClientProvider>
);

3. 测试用了同步查找方法

RTL的screen.getBy*是同步查找方法,而骨架屏是异步出现的,必须用screen.findBy*或waitFor等待元素出现:

test('加载时显示骨架屏', async () => {
  render(/* ... */);

  // 用findByTestId等待骨架屏出现
  const skeleton = await screen.findByTestId('carousel-skeleton');
  expect(skeleton).toBeInTheDocument();
});

记得给组件添加data-testid属性,方便定位:

// MainContent.tsx
if (isLoading) {
  return <CarouselSkeleton data-testid="carousel-skeleton" />;
}
return <Carousel data-testid="carousel" data={data} />;

4. 测试环境配置问题

检查Vitest配置,确保MSW Worker正确注册,且React Query没有被DevTools干扰:

// vitest.config.ts
export default defineConfig({
  test: {
    environment: 'jsdom',
    setupFiles: ['./src/test/setup.ts'], // 这里注册MSW
    globals: true,
  },
});

在setup.ts里注册MSW:

import { setupServer } from 'msw/node';
import { handlers } from './mocks/handlers';

const server = setupServer(...handlers);

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

完整可运行测试代码示例

import { render, screen } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { rest } from 'msw';
import { server } from './mocks/server';
import MainContent from './MainContent';

let queryClient: QueryClient;

beforeEach(() => {
  queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        cacheTime: 0,
        retry: false,
      },
    },
  });
});

test('加载时显示骨架屏,完成后显示轮播', async () => {
  // 临时添加带延迟的请求拦截
  server.use(
    rest.get('/api/categorized-recipes', (req, res, ctx) => {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(res(ctx.json({ recipes: [] })));
        }, 100);
      });
    })
  );

  render(
    <QueryClientProvider client={queryClient}>
      <MainContent />
    </QueryClientProvider>
  );

  // 验证骨架屏存在
  const skeleton = await screen.findByTestId('carousel-skeleton');
  expect(skeleton).toBeInTheDocument();

  // 验证加载完成后轮播出现,骨架屏消失
  const carousel = await screen.findByTestId('carousel');
  expect(carousel).toBeInTheDocument();
  expect(skeleton).not.toBeInTheDocument();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 22:33:11