renderHook跨测试用例数据泄漏问题求助
解决renderHook跨测试用例缓存泄漏问题
问题
单独运行每个测试用例时功能正常,但执行整个describe块时第二个测试失败。执行expect(result.current.data?.items).toHaveLength(3)断言时,实际返回长度为5,明显复用了第一个测试的Hook结果。技术栈为Next.js 14、TanStack Query v5、MSW 2。
原因
- TanStack Query缓存共享:两个测试用了完全相同的
queryKey([LIST_KEY, "0", "5"]),TanStack Query默认会缓存查询结果,导致第二个测试直接读取第一个测试的缓存,没有触发新的请求。 - 未等待请求完成:第二个测试没有用
waitFor等待请求结束,可能在数据还未更新时就执行了断言。 - 缓存未清理:
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
相关产品推荐
相关产品推荐

