使用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
相关产品推荐
相关产品推荐

