如何在Docker运行的集成测试中Mock GRPC服务内的GRPC客户端
解决E2E测试中Mock gRPC客户端C的三种实用方案
针对你不想启动依赖复杂的客户端C容器的需求,下面是几个落地性强的Mock方案:
方案一:依赖注入+测试专用Mock客户端
核心思路是把客户端C的初始化逻辑做成可配置的,通过环境变量切换真实客户端和Mock实现,不需要额外启动服务。
步骤:
- 生成Mock客户端:用
mockery或者github.com/golang/mock工具,根据客户端C的gRPC proto文件生成Mock实现。比如执行mockery --name S2ControlPlaneApiServiceClient --output mocks --outpkg mocks生成Mock代码。 - 修改API服务的客户端初始化逻辑:通过环境变量判断是否启用Mock:
import ( "os" "log" "github.com/golang/mock/gomock" "your-project/protos" "your-project/mocks" ) func main() { // ... 其他初始化逻辑(store、dd、zapLogger等) var client protos.S2ControlPlaneApiServiceClient mockEnabled := os.Getenv("MOCK_CONTROL_PLANE") == "true" if mockEnabled { ctrl := gomock.NewController(nil) defer ctrl.Finish() // 创建Mock客户端并预设方法行为 mockClient := mocks.NewS2ControlPlaneApiServiceClient(ctrl) // 示例:预设SomeMethod的返回值,可根据测试需求添加更多 mockClient.EXPECT().SomeMethod(gomock.Any()).Return(&protos.SomeResponse{}, nil) client = mockClient } else { // 真实客户端连接逻辑 conn, err := grpc.Dial("localhost:38644", grpc.WithInsecure()) if err != nil { log.Fatalf("failed to connect to control plane: %v", err) } defer conn.Close() client = protos.NewS2ControlPlaneApiServiceClient(conn) } s := service.NewDatalakeApiServiceImpl(client, store) // ... 后续gRPC服务器启动逻辑不变 }
- Docker Compose配置环境变量:
datalake_api: build: context: ../../../ dockerfile: go.Dockerfile environment: - MOCK_CONTROL_PLANE=true
优点:
- 不需要额外启动任何服务,Mock逻辑内嵌在API进程中
- 可以精准控制每个方法的返回值,适合针对性测试
缺点:
- Mock逻辑和业务代码耦合,需要维护环境变量判断分支
方案二:本地启动Mock gRPC服务器,容器指向宿主机
核心思路是在E2E测试运行时,本地启动一个Mock gRPC服务器,让容器内的API服务连接这个外部Mock服务。
步骤:
- 编写Mock服务器代码:实现客户端C的gRPC服务接口,预设方法行为:
import ( "net" "log" "github.com/golang/mock/gomock" "google.golang.org/grpc" "your-project/protos" "your-project/mocks" ) func main() { lis, err := net.Listen("tcp", ":38644") if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer() ctrl := gomock.NewController(nil) mockService := mocks.NewS2ControlPlaneApiServiceServer(ctrl) // 预设服务方法的行为,比如处理API服务的调用请求 mockService.EXPECT().SomeMethod(gomock.Any(), gomock.Any()).Return(&protos.SomeResponse{}, nil) protos.RegisterS2ControlPlaneApiServiceServer(grpcServer, mockService) log.Printf("mock control plane server listening on :38644") if err := grpcServer.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
- 修改API服务的连接地址为环境变量配置:
addr := os.Getenv("CONTROL_PLANE_ADDR") if addr == "" { addr = "localhost:38644" } conn, err := grpc.Dial(addr, grpc.WithInsecure()) // ... 后续逻辑
- Docker Compose配置指向宿主机:
datalake_api: build: context: ../../../ dockerfile: go.Dockerfile environment: - CONTROL_PLANE_ADDR=host.docker.internal:38644 # 如果是Linux环境,host.docker.internal可能不生效,需要添加网络配置: # network_mode: "host"
优点:
- Mock逻辑和API服务完全解耦,可独立维护和扩展
- 模拟真实的gRPC网络调用,测试场景更接近生产环境
缺点:
- 需要在测试执行前手动/自动启动Mock服务器
- 跨平台(比如Linux)可能需要调整网络配置
方案三:容器内嵌入轻量Mock服务
核心思路是把Mock服务器打包进API服务的Docker镜像,在测试环境启动时自动启动Mock服务,完全容器化,不需要外部依赖。
步骤:
- 多阶段构建Docker镜像:同时打包API服务和Mock服务器:
# 构建API服务 FROM golang:1.21 as api-builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o datalake-api ./cmd/api # 构建Mock服务器 FROM golang:1.21 as mock-builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o mock-control-plane ./cmd/mock-control-plane # 最终镜像 FROM alpine:latest WORKDIR /app COPY --from=api-builder /app/datalake-api . COPY --from=mock-builder /app/mock-control-plane . COPY entrypoint.sh . RUN chmod +x entrypoint.sh ENTRYPOINT ["./entrypoint.sh"]
- 编写entrypoint启动脚本:判断是否启动Mock服务:
#!/bin/sh # 启动Mock服务(后台运行) if [ "$MOCK_CONTROL_PLANE" = "true" ]; then ./mock-control-plane & fi # 启动API服务 exec ./datalake-api
- Docker Compose配置环境变量:
datalake_api: build: context: ../../../ dockerfile: go.Dockerfile environment: - MOCK_CONTROL_PLANE=true - CONTROL_PLANE_ADDR=localhost:38644
优点:
- 完全容器化,测试环境不需要额外依赖
- 可以在任何支持Docker的环境中运行,一致性强
缺点:
- 镜像体积会略有增加
- Mock服务和API服务共享容器资源,可能存在干扰风险
内容的提问来源于stack exchange,提问作者Anshum17
相关产品推荐
相关产品推荐

