基于NestJS的gRPC微服务单元测试与E2E测试方案咨询
Hey there! 作为常年跟NestJS和gRPC打交道的开发者,我来分享些实际落地的测试经验,希望能帮到你~
一、单元测试:聚焦核心逻辑,隔离gRPC依赖
单元测试的核心是只测你写的代码,把gRPC相关的外部依赖全部mock掉,避免网络或外部服务影响测试结果。
用Nest测试模块隔离依赖
借助@nestjs/testing的Test.createTestingModule,把gRPC客户端/服务端替换成mock实现。比如测试一个依赖gRPC用户服务的订单service:import { Test, TestingModule } from '@nestjs/testing'; import { OrderService } from './order.service'; import { ClientGrpc } from '@nestjs/microservices'; describe('OrderService', () => { let service: OrderService; let mockUserClient: any; beforeEach(async () => { mockUserClient = { getUserById: jest.fn().mockResolvedValue({ id: 1, name: 'Test User' }), }; const module: TestingModule = await Test.createTestingModule({ providers: [ OrderService, { provide: 'USER_SERVICE', // 对应你注入的gRPC服务token useValue: mockUserClient, }, ], }).compile(); service = module.get<OrderService>(OrderService); }); it('should get user info via gRPC client', async () => { const result = await service.getOrderUser(1); expect(mockUserClient.getUserById).toHaveBeenCalledWith({ id: 1 }); expect(result).toEqual({ id: 1, name: 'Test User' }); }); });验证DTO/Proto约束
gRPC依赖proto文件定义的结构,Nest的管道会自动做参数校验。单元测试里要验证:当输入不符合proto定义时,服务是否抛出正确的错误。比如用ValidationPipe配合测试,模拟非法请求:it('should throw error if order id is not a number', async () => { await expect(service.createOrder({ id: 'invalid', amount: 100 })).rejects.toThrow(); });测试切面逻辑(守卫、拦截器)
如果你的gRPC服务用了守卫(比如认证守卫)或拦截器,单独测试这些组件的逻辑。比如测试AuthGuard是否正确解析gRPC请求的metadata里的token:it('should allow request with valid token', async () => { const context = createMockGrpcContext({ metadata: { authorization: 'Bearer valid-token' } }); const guard = new AuthGuard(); const result = await guard.canActivate(context); expect(result).toBe(true); });(这里的
createMockGrpcContext可以自己封装一个工具函数,模拟gRPC的执行上下文)
二、E2E测试:验证完整调用链路
E2E测试要模拟真实的gRPC调用,验证从请求发送到服务处理再到响应返回的完整流程,同时可以mock外部依赖的其他微服务。
启动测试用微服务实例
用Test.createTestingMicroservice创建一个测试环境的微服务,指定gRPC的传输方式和proto文件:import { Test } from '@nestjs/testing'; import { ClientGrpc, Transport } from '@nestjs/microservices'; import { OrderService } from './order.service'; import { join } from 'path'; describe('Order gRPC Service (E2E)', () => { let client: ClientGrpc; let orderService: any; beforeEach(async () => { const module = await Test.createTestingMicroservice({ transport: Transport.GRPC, options: { package: 'order', protoPath: join(__dirname, '../proto/order.proto'), url: 'localhost:50051', }, providers: [OrderService], }).compile(); client = module.get<ClientGrpc>(ClientGrpc); orderService = client.getService('OrderService'); }); afterEach(async () => { await client.close(); }); it('should create an order and return it', async () => { const request = { userId: 1, amount: 200 }; const result = await orderService.createOrder(request).toPromise(); expect(result).toHaveProperty('id'); expect(result.userId).toBe(1); expect(result.amount).toBe(200); }); });Mock外部gRPC服务依赖
如果你的服务依赖其他gRPC微服务(比如用户服务),在E2E测试里可以用jest.mock或者测试模块替换成mock服务,避免依赖真实的外部服务。比如:// 在测试文件顶部mock用户服务客户端 jest.mock('@nestjs/microservices', () => ({ ...jest.requireActual('@nestjs/microservices'), ClientGrpc: jest.fn().mockImplementation(() => ({ getService: jest.fn().mockReturnValue({ getUserById: jest.fn().mockResolvedValue({ id: 1, name: 'Test User' }), }), })), }));测试错误场景与元数据处理
不要只测成功场景,还要验证错误情况:比如发送不符合proto的请求,服务是否返回正确的gRPC错误码;测试metadata里的认证信息是否被正确处理,比如无token时是否返回UNAUTHENTICATED错误。
三、实用技巧
- 用jest的mock功能简化测试:对于重复的gRPC调用mock,可以封装成工具函数,比如
mockGrpcService(methodName, returnValue),减少重复代码。 - 测试覆盖:确保proto文件里定义的所有gRPC方法都被单元测试和E2E测试覆盖到,避免遗漏接口。
- 本地测试环境:E2E测试时可以用docker-compose启动测试用的数据库、消息队列等依赖,保证测试环境和生产环境一致。
内容的提问来源于stack exchange,提问作者Jamie McInerney

