如何Mock Firebase Firestore?Go语言gRPC服务测试问题求助
Firestore Mock 示例及问题解决
问题概述
我有一个基于Firestore的gRPC服务,在测试AddUser方法时遇到两个问题:
- Mock客户端始终查询路径
"projects/projectID/databases/(default)",无法指向正确的项目ID路径 c.AddUser方法仍在调用真实的Firestore,无法完成完整Mock链路
当前尝试使用的库:github.com/rjansen/raizel/firestore/mock和github.com/weathersource/go-mockfs,需要相关示例。
现有gRPC服务代码
const ( bufSize = 1024 * 1024 ) var lis *bufconn.Listener func init() { lis = bufconn.Listen(bufSize) s := grpc.NewServer() app, err := firebase.NewApp(context.Background(), nil) if err != nil { log.Fatalf("error initializing app: %v\n", err) } client, err := app.Firestore(ctx) if err != nil { log.Fatalf("error initializing firestore: %v\n", err) } pb.RegisterBlogServiceServer(s, &server{app: app, firestore: client}) go func() { if err := s.Serve(lis); err != nil { log.Fatalf("Server exited with error: %v\n", err) } }() } func bufDialer(context.Context, string) (net.Conn, error) { return lis.Dial() }
现有测试代码
func Test_server_AddUsers(t *testing.T) { ctx := context.Background() creds := grpc.WithTransportCredentials(insecure.NewCredentials()) conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), creds) if err != nil { t.Fatalf("Failed to dial bufnet: %v", err) } defer conn.Close() c := user.NewUserClient(conn) client, server, err := mockfs.New() assert.NotNil(t, client) assert.NotNil(t, server) assert.Nil(t, err) log.Println("CLIENT1: ", client) // 输出 CLIENT1: &{0xc00073c090 projectID (default)} server.AddRPC( &pb.BatchGetDocumentsRequest{ Database: dbPath, Documents: []string{pathUser}, }, []interface{}{ &pb.BatchGetDocumentsResponse{ Result: &pb.BatchGetDocumentsResponse_Found{docUser}, ReadTime: aTimestamp2, }, }, ) userDocRef := client.Collection(collections.User).Doc(userID) gotUserDoc, err := userDocRef.Get(ctx) pbError, err := c.AddUser(ctx, &user.AddUserRequest{ UserId: userID, }) assert.Nil(t, err) }
解决方案及示例
问题1:Mock路径错误的解决
go-mockfs默认使用projectID作为项目名,需在创建Mock客户端时指定真实项目ID:
// 替换默认项目ID,创建Mock客户端 client, server, err := mockfs.New(mockfs.WithProjectID("your-real-project-id"))
同时注册RPC请求时,Database字段要使用正确的格式:
dbPath := "projects/your-real-project-id/databases/(default)" server.AddRPC( &pb.BatchGetDocumentsRequest{ Database: dbPath, Documents: []string{"projects/your-real-project-id/databases/(default)/documents/users/" + userID}, }, // ...返回内容 )
问题2:完整Mock链路的实现
核心问题是gRPC服务初始化时直接绑定了真实Firestore客户端,需重构代码实现依赖注入:
- 重构gRPC服务结构体,支持依赖注入
// 定义Server结构体,直接依赖Firestore客户端 type server struct { pb.UnimplementedBlogServiceServer firestore *firestore.Client } // 新增构造函数,用于注入Firestore客户端 func NewServer(fsClient *firestore.Client) *server { return &server{firestore: fsClient} }
- 修改初始化逻辑,测试时注入Mock客户端
将原来的init函数改为可配置的启动函数,测试时手动创建服务并注入Mock客户端:
// 生产环境启动函数 func StartServer(fsClient *firestore.Client) *grpc.Server { lis := bufconn.Listen(bufSize) s := grpc.NewServer() pb.RegisterBlogServiceServer(s, NewServer(fsClient)) go func() { if err := s.Serve(lis); err != nil { log.Fatalf("Server exited with error: %v\n", err) } }() return s } // 保留bufDialer函数 func bufDialer(context.Context, string) (net.Conn, error) { return lis.Dial() }
- 修改测试代码,注入Mock客户端
func Test_server_AddUsers(t *testing.T) { ctx := context.Background() // 1. 创建Firestore Mock客户端并指定项目ID mockFSClient, mockServer, err := mockfs.New(mockfs.WithProjectID("your-real-project-id")) assert.Nil(t, err) // 2. 注册AddUser对应的Mock RPC(匹配CreateDocument请求格式) dbPath := "projects/your-real-project-id/databases/(default)" docPath := fmt.Sprintf("%s/documents/users/%s", dbPath, userID) mockServer.AddRPC( &pb.CreateDocumentRequest{ Parent: fmt.Sprintf("%s/documents/users", dbPath), DocumentId: userID, Document: &pb.Document{ Name: docPath, Fields: map[string]*pb.Value{ "userId": {Value: &pb.Value_StringValue{StringValue: userID}}, }, }, }, []interface{}{ &pb.CreateDocumentResponse{ Document: &pb.Document{ Name: docPath, Fields: map[string]*pb.Value{ "userId": {Value: &pb.Value_StringValue{StringValue: userID}}, }, }, UpdateTime: timestamppb.New(time.Now()), }, }, ) // 3. 启动gRPC服务并注入Mock客户端 lis := bufconn.Listen(bufSize) s := grpc.NewServer() pb.RegisterBlogServiceServer(s, NewServer(mockFSClient)) go func() { if err := s.Serve(lis); err != nil { t.Fatalf("Server exited with error: %v\n", err) } }() // 4. 连接gRPC服务并调用AddUser creds := grpc.WithTransportCredentials(insecure.NewCredentials()) conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return lis.Dial() }), creds) assert.Nil(t, err) defer conn.Close() c := user.NewUserClient(conn) pbError, err := c.AddUser(ctx, &user.AddUserRequest{ UserId: userID, }) assert.Nil(t, err) assert.Nil(t, pbError) // 验证Mock是否被调用 assert.Equal(t, 1, mockServer.CallCount()) }
关键说明
- 必须通过依赖注入将Firestore客户端传入gRPC服务,才能在测试时替换为Mock客户端
- 注册Mock RPC时,要严格匹配Firestore API的请求格式(如
CreateDocumentRequest的Parent和DocumentId字段) - 使用
mockfs.WithProjectID指定项目ID,确保路径匹配
内容的提问来源于stack exchange,提问作者nonus25
相关产品推荐
相关产品推荐

