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

基于Jest与React Testing Library的TRPC客户端Mock单元及集成测试封装

用Jest + React Testing Library Mock TRPC客户端(单元/集成测试)

不管是单元测试还是集成测试,Mock TRPC客户端的核心是模拟TRPC hooks的返回值或者拦截API请求,下面分场景给出具体实现:

单元测试:单个组件测试

方案1:直接Mock TRPC Hooks(轻量)

如果组件仅依赖TRPC的某个hook,直接用Jest jest.mock Mock对应的hook,指定加载、成功、失败等状态的返回值,不需要依赖TRPC上下文。

举个例子,测试一个展示帖子列表的组件:

// PostsList.tsx 组件代码
import { trpc } from '../utils/trpc';

export const PostsList = () => {
  const { data: posts, isLoading } = trpc.posts.all.useQuery();

  if (isLoading) return <div>Loading...</div>;
  return (
    <ul>
      {posts?.map(post => <li key={post.id}>{post.title}</li>)}
    </ul>
  );
};

测试代码:

// PostsList.test.tsx
import { render, screen } from '@testing-library/react';
import { PostsList } from './PostsList';
import { trpc } from '../utils/trpc';

// Mock trpc客户端的posts.all hook
jest.mock('../utils/trpc', () => ({
  trpc: {
    posts: {
      all: {
        useQuery: jest.fn(),
      },
    },
  },
}));

describe('PostsList', () => {
  it('显示加载状态', () => {
    // 模拟加载中状态
    (trpc.posts.all.useQuery as jest.Mock).mockReturnValue({
      data: undefined,
      isLoading: true,
    });

    render(<PostsList />);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
  });

  it('成功渲染帖子列表', () => {
    // 模拟返回数据的状态
    const mockPosts = [
      { id: 1, title: '第一篇帖子' },
      { id: 2, title: '第二篇帖子' },
    ];
    (trpc.posts.all.useQuery as jest.Mock).mockReturnValue({
      data: mockPosts,
      isLoading: false,
    });

    render(<PostsList />);
    expect(screen.getByText('第一篇帖子')).toBeInTheDocument();
  });
});

方案2:用TRPC测试工具创建Mock客户端(贴近真实环境)

如果组件依赖TRPC Provider,可以用TRPC官方的@trpc/server/testing创建Mock客户端,配合React Query Provider包裹组件,更贴近实际运行环境。

先安装测试依赖:

npm install -D @trpc/server/testing

测试代码示例:

// PostsList.test.tsx
import { render, screen } from '@testing-library/react';
import { PostsList } from './PostsList';
import { createTRPCReact } from '@trpc/react-query';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { mockTRPC } from '@trpc/server/testing';
import type { AppRouter } from '../server/router'; // 导入你的TRPC路由类型

const trpc = createTRPCReact<AppRouter>();

describe('PostsList', () => {
  it('渲染帖子列表', async () => {
    const queryClient = new QueryClient();
    // 创建Mock TRPC客户端和路由
    const { mockRouter, client } = mockTRPC<AppRouter>();

    // 指定posts.all接口的返回值
    mockRouter.posts.all.mockResolvedValue([
      { id: 1, title: 'Mock测试帖子' },
    ]);

    render(
      <QueryClientProvider client={queryClient}>
        <trpc.Provider client={client} queryClient={queryClient}>
          <PostsList />
        </trpc.Provider>
      </QueryClientProvider>
    );

    // 等待异步数据加载完成
    await screen.findByText('Mock测试帖子');
    expect(screen.getByText('Mock测试帖子')).toBeInTheDocument();
  });
});

集成测试:多组件协作测试

方案1:全局Mock TRPC Router

在测试全局配置文件中创建Mock TRPC客户端,所有测试用例共享这个Mock,适合多组件协作的集成测试。

在setupTests.ts中全局配置:

import { mockTRPC } from '@trpc/server/testing';
import { trpc } from '../utils/trpc';
import type { AppRouter } from '../server/router';

// 创建全局Mock TRPC客户端
const { mockRouter, client } = mockTRPC<AppRouter>();

// 替换项目中的trpc客户端
jest.mock('../utils/trpc', () => ({
  trpc: client,
}));

// 导出mockRouter,方便测试用例中动态修改返回值
export { mockRouter };

集成测试示例(测试包含列表和创建组件的页面):

// PostPage.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { PostPage } from './PostPage';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { trpc } from '../utils/trpc';
import { mockRouter } from '../setupTests';

describe('PostPage', () => {
  const queryClient = new QueryClient();

  it('创建新帖子后自动更新列表', async () => {
    // 初始帖子列表Mock
    mockRouter.posts.all.mockResolvedValue([{ id: 1, title: '初始帖子' }]);
    // 创建帖子接口的Mock返回值
    mockRouter.posts.create.mockResolvedValue({ id: 2, title: '新创建的帖子' });

    render(
      <QueryClientProvider client={queryClient}>
        <trpc.Provider client={trpc} queryClient={queryClient}>
          <PostPage />
        </trpc.Provider>
      </QueryClientProvider>
    );

    // 确认初始列表显示
    await screen.findByText('初始帖子');

    // 模拟用户输入并提交
    fireEvent.change(screen.getByLabelText('帖子标题'), { target: { value: '新创建的帖子' } });
    fireEvent.click(screen.getByText('创建帖子'));

    // 等待列表更新
    await screen.findByText('新创建的帖子');
    expect(screen.getByText('初始帖子')).toBeInTheDocument();
  });
});

方案2:用MSW Mock TRPC HTTP请求(最贴近真实场景)

如果你的TRPC客户端通过HTTP调用后端,可以用MSW(Mock Service Worker)拦截网络请求,模拟真实的API响应,适合端到端风格的集成测试。

先安装MSW:

npm install -D msw

创建MSW请求处理器:

// msw/handlers.ts
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';

// 拦截TRPC的HTTP请求(默认路径是/api/trpc)
const server = setupServer(
  http.post('/api/trpc', async ({ request }) => {
    const body = await request.json();
    const { procedurePath } = body;

    // 根据不同的接口路径返回Mock数据
    if (procedurePath === 'posts.all') {
      return HttpResponse.json({
        result: { data: [{ id: 1, title: 'MSW Mock帖子' }] },
      });
    }

    if (procedurePath === 'posts.create') {
      const { input } = body;
      return HttpResponse.json({
        result: { data: { id: 2, title: input.title } },
      });
    }

    return HttpResponse.json({ error: { message: '接口不存在' } }, { status: 404 });
  })
);

export { server };

在setupTests.ts中启动MSW:

import { server } from './msw/handlers';

// 测试前启动服务
beforeAll(() => server.listen());
// 每个测试后重置处理器
afterEach(() => server.resetHandlers());
// 测试结束后关闭服务
afterAll(() => server.close());

集成测试代码(无需Mock TRPC hooks,直接用真实客户端):

// PostPage.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { PostPage } from './PostPage';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { trpc } from '../utils/trpc';

describe('PostPage', () => {
  const queryClient = new QueryClient();

  it('创建帖子后更新列表', async () => {
    render(
      <QueryClientProvider client={queryClient}>
        <trpc.Provider client={trpc} queryClient={queryClient}>
          <PostPage />
        </trpc.Provider>
      </QueryClientProvider>
    );

    await screen.findByText('MSW Mock帖子');

    // 模拟创建帖子
    fireEvent.change(screen.getByLabelText('帖子标题'), { target: { value: 'MSW新帖子' } });
    fireEvent.click(screen.getByText('创建帖子'));

    await screen.findByText('MSW新帖子');
    expect(screen.getByText('MSW Mock帖子')).toBeInTheDocument();
  });
});

内容的提问来源于stack exchange,提问作者Ayo D Adesanya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 09:46:40