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

如何测试Next.js SSR中用client选项调用useQuery的React组件

解决Next.js SSR中Apollo单例Client测试不通过的问题

核心问题

组件内useQuery硬指定了单例getClient(),而MockedProvider会创建独立的Client实例,两者数据不互通,导致组件读不到Mock数据。

可行解决办法

办法1:让getClient支持自定义缓存并重置单例

修改单例Client创建函数,允许传入自定义缓存,同时在测试时重置单例,确保组件与MockedProvider共享同一缓存:

  1. 更新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;
}
  1. 测试文件中重置并共享缓存:
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,无需修改组件核心逻辑:

  1. 创建自定义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();
};
  1. 修改组件中的useQuery调用:
import { useApolloClient } from '../hooks/useApolloClient';

// ...
const client = useApolloClient();
const { data, loading, error } = useQuery<{ project: IProject }, { id: number }>(
  GET_PROJECT,
  {
    variables: { id },
    client,
  }
);
  1. 测试时直接使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 15:55:41