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

renderHook跨测试用例数据泄漏问题求助

解决renderHook跨测试用例缓存泄漏问题

问题

单独运行每个测试用例时功能正常,但执行整个describe块时第二个测试失败。执行expect(result.current.data?.items).toHaveLength(3)断言时,实际返回长度为5,明显复用了第一个测试的Hook结果。技术栈为Next.js 14、TanStack Query v5、MSW 2。

原因

  1. TanStack Query缓存共享:两个测试用了完全相同的queryKey([LIST_KEY, "0", "5"]),TanStack Query默认会缓存查询结果,导致第二个测试直接读取第一个测试的缓存,没有触发新的请求。
  2. 未等待请求完成:第二个测试没有用waitFor等待请求结束,可能在数据还未更新时就执行了断言。
  3. 缓存未清理:server.resetHandlers()仅重置了MSW的请求处理器,但没有清理TanStack Query的缓存。

解决方案

方案1:每个测试使用独立的QueryClient

为每个测试创建全新的QueryClient,彻底隔离缓存,避免跨测试用例共享数据。同时确保每个测试都等待请求完成后再断言。

方案2:测试后清空缓存

使用同一个QueryClient,但在每个测试结束后调用queryClient.clear()清空缓存,也能达到隔离效果。

完整修正代码

方案1实现(推荐)

import { ReactNode } from 'react';
import { renderHook, waitFor } from '@testing-library/react';
import { server } from '@/lib/http-mocks/server';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useCaseListData } from './useCaseListData';

// 统一定义QueryWrapper组件
const QueryWrapper = ({ children, queryClient }: { children: ReactNode; queryClient: QueryClient }) => (
  <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);

beforeEach(() => {
  server.resetHandlers();
});

describe("useCaseListData测试", () => {
  it('获取无偏移的案例列表数据', async () => {
    server.use(listMock);
    // 每个测试创建独立的QueryClient
    const queryClient = new QueryClient();
    const { result } = renderHook(() => useCaseListData(0, 5), {
      wrapper: ({ children }) => <QueryWrapper queryClient={queryClient}>{children}</QueryWrapper>,
    });

    // 等待加载完成
    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.error).toBe(null);
    expect(result.current.data?.items).toHaveLength(5);
  });

  it('获取存在缺失值的案例列表', async () => {
    server.use(missingValuesMock);
    const queryClient = new QueryClient();
    const { result } = renderHook(() => useCaseListData(0, 5), {
      wrapper: ({ children }) => <QueryWrapper queryClient={queryClient}>{children}</QueryWrapper>,
    });

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.error).toBe(null);
    expect(result.current.data?.items).toHaveLength(3);
  });
});

// 自定义Hook保持不变
export function useCaseListData(page: number, pageSize: number): UseQueryResult<CListDataResult> {
  const result = useQuery<ListDataResult>({
    queryKey: [LIST_KEY, `${page}`, `${pageSize}`],
    queryFn: () => getList(page, pageSize),
    throwOnError: true
  });

  return result;
}

方案2实现

import { ReactNode } from 'react';
import { renderHook, waitFor } from '@testing-library/react';
import { server } from '@/lib/http-mocks/server';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useCaseListData } from './useCaseListData';

let queryClient: QueryClient;
const QueryWrapper = ({ children }: { children: ReactNode }) => (
  <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);

beforeEach(() => {
  server.resetHandlers();
  queryClient = new QueryClient();
});

// 每个测试后清空缓存
afterEach(() => {
  queryClient.clear();
});

describe("useCaseListData测试", () => {
  it('获取无偏移的案例列表数据', async () => {
    server.use(listMock);
    const { result } = renderHook(() => useCaseListData(0, 5), { wrapper: QueryWrapper });

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.error).toBe(null);
    expect(result.current.data?.items).toHaveLength(5);
  });

  it('获取存在缺失值的案例列表', async () => {
    server.use(missingValuesMock);
    const { result } = renderHook(() => useCaseListData(0, 5), { wrapper: QueryWrapper });

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.error).toBe(null);
    expect(result.current.data?.items).toHaveLength(3);
  });
});

// 自定义Hook保持不变
export function useCaseListData(page: number, pageSize: number): UseQueryResult<CListDataResult> {
  const result = useQuery<ListDataResult>({
    queryKey: [LIST_KEY, `${page}`, `${pageSize}`],
    queryFn: () => getList(page, pageSize),
    throwOnError: true
  });

  return result;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 23:43:10