基于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
相关产品推荐
相关产品推荐

