如何测试Next.js SSR中用client选项调用useQuery的React组件
解决Next.js SSR中Apollo单例Client测试不通过的问题
核心问题
组件内useQuery硬指定了单例getClient(),而MockedProvider会创建独立的Client实例,两者数据不互通,导致组件读不到Mock数据。
可行解决办法
办法1:让getClient支持自定义缓存并重置单例
修改单例Client创建函数,允许传入自定义缓存,同时在测试时重置单例,确保组件与MockedProvider共享同一缓存:
- 更新getClient函数:
// getClient.ts import { ApolloClient, InMemoryCache } from '@apollo/client'; let client: ApolloClient<any> | null = null; export default function getClient(customCache?: InMemoryCache) { // 已有client且未传自定义缓存时直接返回 if (client && !customCache) return client; client = new ApolloClient({ uri: process.env.NEXT_PUBLIC_API_URL, cache: customCache || new InMemoryCache(), // 你的SSR配置(如ssrMode等) }); return client; } // 新增重置函数用于测试 export function resetApolloClient() { client = null; }
- 测试文件中重置并共享缓存:
import getClient, { resetApolloClient } from '../path-to-your-getClient'; import { InMemoryCache, MockedProvider } from '@apollo/client/testing'; beforeEach(() => { // 每次测试前重置单例 resetApolloClient(); }); it('能正确渲染Mock项目数据', async () => { const cache = new InMemoryCache({ addTypename: false }); // 用自定义缓存创建Client,确保组件使用该实例 getClient(cache); renderWithProvider( <MockedProvider addTypename={false} mocks={[mockedProjectRequest]} cache={cache}> <Project /> </MockedProvider>, store, "/project/112551", ); // 等待数据加载并断言 await waitFor(() => { expect(screen.getByText('你的Mock项目名称')).toBeInTheDocument(); }); });
办法2:封装自定义Hook实现Client自动切换
通过自定义Hook优先使用Context中的Client(MockedProvider提供), fallback到单例Client,无需修改组件核心逻辑:
- 创建自定义Hook:
// hooks/useApolloClient.ts import { useApolloClient as useBaseClient } from '@apollo/client'; import getClient from '../path-to-your-getClient'; export const useApolloClient = () => { // 优先取MockedProvider注入的Context Client const contextClient = useBaseClient(); return contextClient || getClient(); };
- 修改组件中的useQuery调用:
import { useApolloClient } from '../hooks/useApolloClient'; // ... const client = useApolloClient(); const { data, loading, error } = useQuery<{ project: IProject }, { id: number }>( GET_PROJECT, { variables: { id }, client, } );
- 测试时直接使用MockedProvider:
it('能正确渲染Mock项目数据', async () => { renderWithProvider( <MockedProvider addTypename={false} mocks={[mockedProjectRequest]}> <Project /> </MockedProvider>, store, "/project/112551", ); await waitFor(() => { expect(screen.getByText('你的Mock项目名称')).toBeInTheDocument(); }); });
办法3:Mock getClient函数返回测试用Client
如果不想修改业务代码,直接在测试中Mock单例Client的返回值:
import getClient from '../path-to-your-getClient'; import { ApolloClient, InMemoryCache, MockedProvider } from '@apollo/client/testing'; // Mock getClient函数 jest.mock('../path-to-your-getClient'); it('能正确渲染Mock项目数据', async () => { const testCache = new InMemoryCache({ addTypename: false }); const testClient = new ApolloClient({ uri: 'test-url', cache: testCache, }); // 让getClient返回测试用Client (getClient as jest.Mock).mockReturnValue(testClient); // 提前写入Mock数据到缓存(或用MockedProvider拦截请求) await testClient.cache.writeQuery({ query: GET_PROJECT, variables: { id: 112551 }, data: { project: /* 你的Mock数据 */ }, }); renderWithProvider( <Project />, store, "/project/112551", ); await waitFor(() => { expect(screen.getByText('你的Mock项目名称')).toBeInTheDocument(); }); });
内容的提问来源于stack exchange,提问作者Amand Witters
相关产品推荐
相关产品推荐

