全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-18 10 分钟 ✍️ juanwangdev

Gin Mock测试

Mock 测试通过模拟外部依赖,实现单元测试的隔离和可控性。

Mock 基础概念

为什么需要 Mock

Go
单元测试原则:
- 测试单个单元,不依赖外部系统
- 数据库、网络服务、文件系统等应 Mock
- 测试结果可重复、可预测
- 测试执行快速、无副作用

接口 Mock

定义接口

Go
// 定义数据访问接口
type UserRepository interface {
    FindByID(id string) (*User, error)
    FindAll() ([]*User, error)
    Create(user *User) error
    Update(user *User) error
    Delete(id string) error
}

// Handler 依赖接口而非具体实现
type UserHandler struct {
    repo UserRepository
}

func (h *UserHandler) GetUser(c *gin.Context) {
    id := c.Param("id")
    user, err := h.repo.FindByID(id)
    if err != nil {
        c.JSON(404, gin.H{"error": "用户不存在"})
        return
    }
    c.JSON(200, user)
}

手动 Mock 实现

Go
type MockUserRepository struct {
    users map[string]*User
}

func NewMockUserRepository() *MockUserRepository {
    return &MockUserRepository{
        users: map[string]*User{
            "1": &User{ID: "1", Name: "Alice", Email: "alice@example.com"},
            "2": &User{ID: "2", Name: "Bob", Email: "bob@example.com"},
        },
    }
}

func (m *MockUserRepository) FindByID(id string) (*User, error) {
    if user, exists := m.users[id]; exists {
        return user, nil
    }
    return nil, errors.New("用户不存在")
}

func (m *MockUserRepository) Create(user *User) error {
    m.users[user.ID] = user
    return nil
}

func (m *MockUserRepository) Delete(id string) error {
    if _, exists := m.users[id]; exists {
        delete(m.users, id)
        return nil
    }
    return errors.New("用户不存在")
}

使用 Mock 测试

Bash
func TestGetUserHandler_WithMock(t *testing.T) {
    mockRepo := NewMockUserRepository()
    handler := &UserHandler{repo: mockRepo}

    r := gin.New()
    r.GET("/users/:id", handler.GetUser)

    // 测试正常情况
    req := httptest.NewRequest("GET", "/users/1", nil)
    w := httptest.NewRecorder()
    r.ServeHTTP(w, req)

    assert.Equal(t, 200, w.Code)

    var user User
    json.Unmarshal(w.Body.Bytes(), &user)
    assert.Equal(t, "Alice", user.Name)

    // 测试不存在情况
    req = httptest.NewRequest("GET", "/users/999", nil)
    w = httptest.NewRecorder()
    r.ServeHTTP(w, req)

    assert.Equal(t, 404, w.Code)
}

gomock 自动生成

安装工具

Go
go install github.com/golang/mock/mockgen@latest

定义接口并生成 Mock

Bash
// interfaces.go
package repository

type UserRepository interface {
    FindByID(id string) (*User, error)
    Create(user *User) error
}

//go:generate mockgen -source=interfaces.go -destination=mock_repository.go -package=repository
Go
# 生成 Mock 文件
go generate ./...

使用 gomock 测试

Go
func TestGetUserHandler_Gomock(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    mockRepo := NewMockUserRepository(ctrl)

    // 设置预期行为
    mockRepo.EXPECT().
        FindByID("1").
        Return(&User{ID: "1", Name: "Alice"}, nil)

    handler := &UserHandler{repo: mockRepo}

    r := gin.New()
    r.GET("/users/:id", handler.GetUser)

    req := httptest.NewRequest("GET", "/users/1", nil)
    w := httptest.NewRecorder()
    r.ServeHTTP(w, req)

    assert.Equal(t, 200, w.Code)
}

func TestGetUserHandler_Error(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()

    mockRepo := NewMockUserRepository(ctrl)

    // 设置错误预期
    mockRepo.EXPECT().
        FindByID("999").
        Return(nil, errors.New("用户不存在"))

    handler := &UserHandler{repo: mockRepo}

    r := gin.New()
    r.GET("/users/:id", handler.GetUser)

    req := httptest.NewRequest("GET", "/users/999", nil)
    w := httptest.NewRecorder()
    r.ServeHTTP(w, req)

    assert.Equal(t, 404, w.Code)
}

gomock 预期设置

Go
// 多次调用
mockRepo.EXPECT().
    FindByID(gomock.Any()).
    Return(&User{ID: "1"}, nil).
    Times(3)

// 任意顺序
mockRepo.EXPECT().
    FindByID("1").
    Return(&User{ID: "1"}, nil).
    AnyTimes()

// 参数匹配
mockRepo.EXPECT().
    Create(gomock.Any()).  // 任意参数
    Return(nil)

// 自定义匹配器
mockRepo.EXPECT().
    Create(&User{Name: "Alice"}).
    Return(nil)

// 返回多次不同结果
mockRepo.EXPECT().
    FindByID("1").
    Return(&User{ID: "1"}, nil).
    Return(nil, errors.New("并发冲突"))

数据库 Mock

GORM Mock

Go
type MockDB struct {
    data map[string]interface{}
}

func (m *MockDB) Find(dest interface{}, conds ...interface{}) error {
    // 模拟查询
    return nil
}

func (m *MockDB) Create(value interface{}) error {
    // 模拟创建
    return nil
}

// 使用 sqlmock
import "github.com/DATA-DOG/go-sqlmock"

func TestDatabaseMock(t *testing.T) {
    db, mock, err := sqlmock.New()
    if err != nil {
        t.Fatal(err)
    }
    defer db.Close()

    // 设置查询预期
    rows := sqlmock.NewRows([]string{"id", "name"}).
        AddRow(1, "Alice").
        AddRow(2, "Bob")

    mock.ExpectQuery("SELECT (.+) FROM users").
        WillReturnRows(rows)

    // 执行测试
    // ...
}

HTTP 服务 Mock

Mock 第三方 API

Go
type APIClient interface {
    GetUser(id string) (*User, error)
}

type MockAPIClient struct {
    response *User
    err      error
}

func (m *MockAPIClient) GetUser(id string) (*User, error) {
    return m.response, m.err
}

func TestExternalAPIMock(t *testing.T) {
    mockClient := &MockAPIClient{
        response: &User{ID: "1", Name: "External User"},
    }

    handler := ExternalAPIHandler{client: mockClient}

    r := gin.New()
    r.GET("/external/:id", handler.GetUser)

    req := httptest.NewRequest("GET", "/external/1", nil)
    w := httptest.NewRecorder()
    r.ServeHTTP(w, req)

    assert.Equal(t, 200, w.Code)
}

httptest Mock Server

Go
func TestMockServer(t *testing.T) {
    // 创建 Mock 服务器
    mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(200)
        w.Write([]byte(`{"id":"1","name":"Mock User"}`))
    }))
    defer mockServer.Close()

    // 使用 Mock 服务器地址
    client := NewAPIClient(mockServer.URL)

    user, err := client.GetUser("1")
    assert.NoError(t, err)
    assert.Equal(t, "Mock User", user.Name)
}

缓存 Mock

Go
type CacheService interface {
    Get(key string) (string, error)
    Set(key string, value string, ttl time.Duration) error
}

type MockCache struct {
    data map[string]string
}

func NewMockCache() *MockCache {
    return &MockCache{data: make(map[string]string)}
}

func (m *MockCache) Get(key string) (string, error) {
    if val, exists := m.data[key]; exists {
        return val, nil
    }
    return "", errors.New("不存在")
}

func (m *MockCache) Set(key string, value string, ttl time.Duration) error {
    m.data[key] = value
    return nil
}

func TestWithCacheMock(t *testing.T) {
    mockCache := NewMockCache()
    mockCache.Set("user:1", `{"id":"1","name":"Cached"}`)

    handler := CachedHandler{cache: mockCache}

    r := gin.New()
    r.GET("/cached/:id", handler.Get)

    req := httptest.NewRequest("GET", "/cached/1", nil)
    w := httptest.NewRecorder()
    r.ServeHTTP(w, req)

    assert.Equal(t, 200, w.Code)
    assert.Contains(t, w.Body.String(), "Cached")
}

依赖注入模式

text
// 应用结构
type Application struct {
    UserRepo   UserRepository
    Cache      CacheService
    APIClient  APIClient
}

func NewApplicationForTest() *Application {
    return &Application{
        UserRepo:  NewMockUserRepository(),
        Cache:     NewMockCache(),
        APIClient: &MockAPIClient{},
    }
}

func TestApplication(t *testing.T) {
    app := NewApplicationForTest()
    r := SetupRouter(app)

    req := httptest.NewRequest("GET", "/users/1", nil)
    w := httptest.NewRecorder()
    r.ServeHTTP(w, req)

    assert.Equal(t, 200, w.Code)
}

Mock 最佳实践

实践说明
接口定义依赖接口而非具体实现
控制器管理gomock.NewController(t)
预期验证ctrl.Finish() 检查预期是否满足
参数匹配gomock.Any() 匹配任意参数
次数控制Times(n)AnyTimes()

注意:Mock 应简化,避免 Mock 内部实现复杂逻辑。

要点总结

  1. 接口定义:依赖接口而非具体实现
  2. 手动 Mock:简单场景手动实现
  3. gomock:自动生成 Mock,支持预期验证
  4. 数据库 Mock:sqlmock 或接口 Mock
  5. HTTP Mock:httptest.NewServer 模拟外部服务
  6. 依赖注入:测试时注入 Mock 实现

📝 发现内容有误?点击此处直接编辑

← 上一篇 Gin 速率限制与防暴力破解
下一篇 → Gin pprof性能分析
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库