Mock策略
本文档定义了井云服务中心的Mock策略,包括Gateway服务的函数字段Mock策略和数据库服务的sqlmock + Ent ORM策略。
Mock策略概述
Gateway服务Mock策略
策略类型: 函数字段Mock
适用服务: Gateway
Mock对象: gRPC客户端
数据库服务Mock策略
策略类型: sqlmock + Ent ORM
适用服务: Agent、Auth、User、Tenant、Payment、Integration、Cron
Mock对象: 数据库连接
Gateway服务Mock策略
Mock结构体定义
type MockAgentClient struct {
CreateAgentFunc func(ctx context.Context, in *agentv1.CreateAgentRequest, opts ...grpc.CallOption) (*agentv1.CreateAgentReply, error)
UpdateAgentFunc func(ctx context.Context, in *agentv1.UpdateAgentRequest, opts ...grpc.CallOption) (*agentv1.UpdateAgentReply, error)
GetAgentFunc func(ctx context.Context, in *agentv1.GetAgentRequest, opts ...grpc.CallOption) (*agentv1.GetAgentReply, error)
}
Mock函数实现
mockClient := &MockAgentClient{
CreateAgentFunc: func(ctx context.Context, in *agentv1.CreateAgentRequest, opts ...grpc.CallOption) (*agentv1.CreateAgentReply, error) {
// 验证传入参数
assert.NotNil(t, in)
assert.Equal(t, int64(1), in.TenantId)
assert.Equal(t, "测试智能体", in.Name)
// 返回Mock数据
return &agentv1.CreateAgentReply{
Id: 1,
Name: "测试智能体",
}, nil
},
}
Mock使用示例
func TestAgentService_CreateAgent_Success(t *testing.T) {
// 1. 创建Mock客户端
mockClient := &MockAgentClient{
CreateAgentFunc: func(ctx context.Context, in *agentv1.CreateAgentRequest, opts ...grpc.CallOption) (*agentv1.CreateAgentReply, error) {
return &agentv1.CreateAgentReply{
Id: 1,
Name: "测试智能体",
Type: agentv1.AgentTypeBOT,
}, nil
},
}
// 2. 创建服务
svc := NewAgentService(mockClient, logger)
// 3. 执行测试
req := &agentv1.CreateAgentRequest{
TenantId: 1,
Name: "测试智能体",
Type: agentv1.AgentTypeBOT,
}
resp, err := svc.CreateAgent(context.Background(), req)
// 4. 验证结果
require.NoError(t, err)
require.NotNil(t, resp)
require.Equal(t, int64(1), resp.Id)
}
数据库服务Mock策略
Setup函数
func setupServiceTest(t *testing.T) (*sql.DB, sqlmock.Sqlmock, *AgentService) {
// 1. 创建 sqlmock
mockDB, mock, err := sqlmock.New()
require.NoError(t, err)
// 2. 使用 Ent driver 包装 mock DB
drv := entsql.OpenDB("postgres", mockDB)
entClient := entclient.NewClient(entclient.Driver(drv))
// 3. 初始化服务
logger := kmlog.NewStdLogger(io.Discard)
data := dataagent.NewData(mockDB, entClient, logger)
repo := dataagent.NewAgentRepo(data)
uc := bizagent.NewAgentUsecase(repo, logger)
svc := NewAgentService(uc, logger)
return mockDB, mock, svc
}
Mock SQL INSERT
func TestCreateAgent_Success(t *testing.T) {
mockDB, mock, svc := setupServiceTest(t)
defer mockDB.Close()
// Mock INSERT 返回 ID
rows := sqlmock.NewRows([]string{"id"}).AddRow(int64(1))
mock.ExpectQuery(`INSERT INTO "agents"`).
WithArgs(1, "测试智能体", "bot").
WillReturnRows(rows)
// 执行测试
resp, err := svc.CreateAgent(context.Background(), req)
// 验证结果
require.NoError(t, err)
require.Equal(t, int64(1), resp.Id)
require.NoError(t, mock.ExpectationsWereMet())
}
Mock SQL SELECT - 单行
func TestGetAgent_Success(t *testing.T) {
mockDB, mock, svc := setupServiceTest(t)
defer mockDB.Close()
now := time.Now()
rows := sqlmock.NewRows([]string{
"id", "tenant_id", "name", "type", "is_active", "created_at", "updated_at",
}).AddRow(
int64(1), int64(1), "测试智能体", "bot", true, now, now,
)
mock.ExpectQuery(`SELECT .* FROM "agents"`).
WithArgs(int64(1)).
WillReturnRows(rows)
// 执行测试
resp, err := svc.GetAgent(context.Background(), &agentv1.GetAgentRequest{Id: 1})
// 验证结果
require.NoError(t, err)
require.Equal(t, int64(1), resp.Agent.Id)
require.NoError(t, mock.ExpectationsWereMet())
}
Mock SQL SELECT - 多行
func TestListAgents_Success(t *testing.T) {
mockDB, mock, svc := setupServiceTest(t)
defer mockDB.Close()
now := time.Now()
rows := sqlmock.NewRows([]string{
"id", "tenant_id", "name", "type", "is_active", "created_at", "updated_at",
}).
AddRow(int64(1), int64(1), "智能体1", "bot", true, now, now).
AddRow(int64(2), int64(1), "智能体2", "workflow", true, now, now)
mock.ExpectQuery(`SELECT .* FROM "agents"`).
WithArgs(int64(1), true).
WillReturnRows(rows)
// 执行测试
resp, err := svc.ListAgents(context.Background(), &agentv1.ListAgentsRequest{
TenantId: 1,
IsActive: true,
})
// 验证结果
require.NoError(t, err)
require.Len(t, resp.Agents, 2)
require.NoError(t, mock.ExpectationsWereMet())
}
Mock SQL UPDATE
func TestUpdateAgent_Success(t *testing.T) {
mockDB, mock, svc := setupServiceTest(t)
defer mockDB.Close()
// Mock UPDATE 返回影响行数
mock.ExpectExec(`UPDATE "agents"`).
WithArgs("新名称", int64(1)).
WillReturnResult(sqlmock.NewResult(1, 1))
// 执行测试
resp, err := svc.UpdateAgent(context.Background(), &agentv1.UpdateAgentRequest{
Id: 1,
Name: "新名称",
})
// 验证结果
require.NoError(t, err)
require.NoError(t, mock.ExpectationsWereMet())
}
Mock SQL错误
func TestCreateAgent_DatabaseError(t *testing.T) {
mockDB, mock, svc := setupServiceTest(t)
defer mockDB.Close()
// Mock数据库错误
mock.ExpectQuery(`INSERT INTO "agents"`).
WillReturnError(errors.New("database connection failed"))
// 执行测试
resp, err := svc.CreateAgent(context.Background(), req)
// 验证结果
require.Error(t, err)
require.Nil(t, resp)
require.NoError(t, mock.ExpectationsWereMet())
}
Mock数据管理规范
Mock数据一致性
Mock返回的数据必须包含所有字段:
// ✅ 正确 - 包含所有字段
Agent: &agentv1.Agent{
Id: 1,
TenantId: 1,
Name: "测试智能体",
Type: agentv1.AgentTypeBOT,
IsActive: true,
CreatedAt: 1672531200,
UpdatedAt: 1672531200,
}
// ❌ 错误 - 字段缺失
Agent: &agentv1.Agent{
Id: 1,
TenantId: 1,
Name: "测试智能体",
Type: agentv1.AgentTypeBOT,
// 缺少 IsActive, CreatedAt, UpdatedAt
}
Mock数据工厂
使用工厂方法创建Mock数据:
func createTestAgent(id int64, tenantId int64, opts ...func(*agentv1.Agent)) *agentv1.Agent {
a := &agentv1.Agent{
Id: id,
TenantId: tenantId,
Name: "测试智能体",
Type: agentv1.AgentTypeBOT,
IsActive: true,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
for _, opt := range opts {
opt(a)
}
return a
}
// 使用示例
agent1 := createTestAgent(1, 1)
agent2 := createTestAgent(2, 1, func(a *agentv1.Agent) {
a.Name = "自定义名称"
a.Type = agentv1.AgentTypeWORKFLOW
})
Mock最佳实践
1. 使用工厂方法创建Mock数据
// ✅ 正确
agent := createTestAgent(1, 1)
// ❌ 错误
agent := &agentv1.Agent{
Id: 1,
TenantId: 1,
Name: "测试智能体",
Type: agentv1.AgentTypeBOT,
IsActive: true,
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
2. 验证所有Mock期望
// ✅ 正确
defer mockDB.Close()
// ... 测试代码
require.NoError(t, mock.ExpectationsWereMet())
// ❌ 错误
// 缺少 期望验证
3. Mock数据包含所有字段
// ✅ 正确
Agent: &agentv1.Agent{
Id: 1,
TenantId: 1,
Name: "测试智能体",
Type: agentv1.AgentTypeBOT,
IsActive: true,
CreatedAt: 1672531200,
UpdatedAt: 1672531200,
}
// ❌ 错误
Agent: &agentv1.Agent{
Id: 1,
TenantId: 1,
Name: "测试智能体",
Type: agentv1.AgentTypeBOT,
// 缺少字段
}
4. 使用正则表达式匹配SQL
// ✅ 正确
mock.ExpectQuery(`SELECT .* FROM "agents"`)
// ❌ 错误
mock.ExpectQuery(`SELECT id, tenant_id, name FROM agents WHERE id = $1`)
5. 在Mock函数中验证输入参数
// ✅ 正确
mockClient.CreateAgentFunc = func(ctx context.Context, in *agentv1.CreateAgentRequest, opts ...grpc.CallOption) (*agentv1.CreateAgentReply, error) {
assert.Equal(t, int64(1), in.TenantId)
assert.Equal(t, "测试智能体", in.Name)
return &agentv1.CreateAgentReply{}, nil
}
// ❌ 错误
mockClient.CreateAgentFunc = func(ctx context.Context, in *agentv1.CreateAgentRequest, opts ...grpc.CallOption) (*agentv1.CreateAgentReply, error) {
// 不验证输入参数
return &agentv1.CreateAgentReply{}, nil
}
Mock常见问题
Q: 如何Mock复杂的SQL查询?
A: 使用正则表达式匹配SQL模式,忽略具体参数值:
mock.ExpectQuery(`SELECT .* FROM "agents" WHERE (.*)`).
WithArgs(int64(1), true).
WillReturnRows(rows)
Q: 如何Mock事务 操作?
A: 使用sqlmock的事务支持:
mock.ExpectBegin()
mock.ExpectExec(`INSERT INTO "agents"`).
WithArgs(1, "测试智能体").
WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
Q: 如何Mock时间字段?
A: 使用固定的时间戳:
now := time.Date(2025, 12, 29, 0, 0, 0, 0, time.UTC)
rows := sqlmock.NewRows([]string{"created_at"}).
AddRow(now)
参考资料
最后更新: 2025-12-29