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

测试与 Mock 实践

单元测试需隔离数据库依赖,本文介绍使用 sqlmock 模拟数据库进行 GORM 测试的实践。

环境准备

安装依赖

Bash
go get github.com/DATA-DOG/go-sqlmock

初始化 Mock

Go
import (
    "testing"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "github.com/DATA-DOG/go-sqlmock"
)

func setupMock(t *testing.T) (*gorm.DB, sqlmock.Sqlmock) {
    mockDB, mock, err := sqlmock.New()
    if err != nil {
        t.Fatalf("failed to create sqlmock: %v", err)
    }
    
    // 将 mock 连接传入 GORM
    dialector := mysql.New(mysql.Config{
        Conn:                      mockDB,
        SkipInitializeWithVersion: true,
    })
    
    db, err := gorm.Open(dialector, &gorm.Config{})
    if err != nil {
        t.Fatalf("failed to open gorm: %v", err)
    }
    
    return db, mock
}

CRUD 测试

查询测试

Go
func TestFindUser(t *testing.T) {
    db, mock := setupMock(t)
    
    // 预期 SQL
    expectedSQL := "SELECT * FROM `users` WHERE `users`.`id` = ?"
    
    // 设置期望与返回
    rows := sqlmock.NewRows([]string{"id", "name", "email"}).
        AddRow(1, "test", "test@example.com")
    
    mock.ExpectQuery(regexp.QuoteMeta(expectedSQL)).
        WithArgs(1).
        WillReturnRows(rows)
    
    // 执行查询
    var user User
    err := db.First(&user, 1).Error
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    
    // 验证
    if user.Name != "test" {
        t.Errorf("expected name=test, got %s", user.Name)
    }
    
    // 确认所有期望都被满足
    if err := mock.ExpectationsWereMet(); err != nil {
        t.Errorf("unfulfilled expectations: %v", err)
    }
}

插入测试

Go
func TestCreateUser(t *testing.T) {
    db, mock := setupMock(t)
    
    mock.ExpectBegin()
    mock.ExpectExec(regexp.QuoteMeta(
        "INSERT INTO `users` (`name`,`email`) VALUES (?,?)")).
        WithArgs("test", "test@example.com").
        WillReturnResult(sqlmock.NewResult(1, 1))
    mock.ExpectCommit()
    
    user := User{Name: "test", Email: "test@example.com"}
    err := db.Create(&user).Error
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    
    if err := mock.ExpectationsWereMet(); err != nil {
        t.Errorf("unfulfilled expectations: %v", err)
    }
}

更新测试

Go
func TestUpdateUser(t *testing.T) {
    db, mock := setupMock(t)
    
    mock.ExpectBegin()
    mock.ExpectExec(regexp.QuoteMeta(
        "UPDATE `users` SET `name`=? WHERE `id` = ?")).
        WithArgs("updated", 1).
        WillReturnResult(sqlmock.NewResult(0, 1))
    mock.ExpectCommit()
    
    err := db.Model(&User{}).Where("id = ?", 1).Update("name", "updated").Error
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
}

删除测试

Go
func TestDeleteUser(t *testing.T) {
    db, mock := setupMock(t)
    
    mock.ExpectBegin()
    mock.ExpectExec(regexp.QuoteMeta(
        "DELETE FROM `users` WHERE `users`.`id` = ?")).
        WithArgs(1).
        WillReturnResult(sqlmock.NewResult(0, 1))
    mock.ExpectCommit()
    
    err := db.Delete(&User{}, 1).Error
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
}

事务测试

事务提交

Go
func TestTransactionCommit(t *testing.T) {
    db, mock := setupMock(t)
    
    mock.ExpectBegin()
    mock.ExpectExec("INSERT INTO").WillReturnResult(sqlmock.NewResult(1, 1))
    mock.ExpectExec("UPDATE").WillReturnResult(sqlmock.NewResult(0, 1))
    mock.ExpectCommit()
    
    err := db.Transaction(func(tx *gorm.DB) error {
        tx.Create(&User{Name: "user1"})
        tx.Model(&User{}).Where("id = ?", 1).Update("name", "updated")
        return nil
    })
    
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
}

事务回滚

Go
func TestTransactionRollback(t *testing.T) {
    db, mock := setupMock(t)
    
    mock.ExpectBegin()
    mock.ExpectExec("INSERT INTO").WillReturnResult(sqlmock.NewResult(1, 1))
    mock.ExpectRollback()
    
    err := db.Transaction(func(tx *gorm.DB) error {
        tx.Create(&User{Name: "user1"})
        return errors.New("business error") // 触发回滚
    })
    
    if err == nil {
        t.Fatal("expected error, got nil")
    }
}

注意事项

regexp.QuoteMeta 用于转义 GORM 生成的 SQL 中的反引号等字符,确保正则匹配正确。

复杂查询的 SQL 可能因驱动不同而变化,测试时应使用与生产相同的驱动。

sqlmock 不验证 SQL 语义正确性,仅匹配字符串模式。

预期顺序必须与实际执行顺序一致,否则测试失败。

要点总结

  • sqlmock 通过模拟 *sql.DB 连接实现 GORM 测试
  • 每个 CRUD 操作需设置对应的 Expect 与 Return
  • 事务测试需匹配 Begin/Commit/Rollback 序列
  • 使用 ExpectationsWereMet() 验证所有期望被满足
  • Mock 测试不依赖真实数据库,执行速度快

存放路径:D:\git2\jwdev\articles\GORM\专家\生态与工具链\测试与 Mock 实践.md

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

← 上一篇 gen 代码生成器使用
下一篇 → 第三方驱动适配
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

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

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