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

如何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客户端,需重构代码实现依赖注入:

  1. 重构gRPC服务结构体,支持依赖注入
// 定义Server结构体,直接依赖Firestore客户端
type server struct {
    pb.UnimplementedBlogServiceServer
    firestore *firestore.Client
}

// 新增构造函数,用于注入Firestore客户端
func NewServer(fsClient *firestore.Client) *server {
    return &server{firestore: fsClient}
}
  1. 修改初始化逻辑,测试时注入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()
}
  1. 修改测试代码,注入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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:30:47