📖 Golang Toàn tập - Database với GORM và Testing
80 phút

Database với GORM và Testing trong Go

GORM Setup

Cài đặt

go get -u gorm.io/gorm
go get -u gorm.io/driver/postgres
go get -u gorm.io/driver/sqlite

Kết nối database

package database

import (
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func Connect(dsn string) (*gorm.DB, error) {
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }
    return db, nil
}

Models và Migrations

Model definition

type User struct {
    gorm.Model
    Name     string `gorm:"not null"`
    Email    string `gorm:"uniqueIndex;not null"`
    Age      int
    Posts    []Post
}

type Post struct {
    gorm.Model
    Title   string `gorm:"not null"`
    Content string
    UserID  uint
    User    User
}

AutoMigrate

db.AutoMigrate(&User{}, &Post{})

CRUD Operations

Create

user := User{Name: "John", Email: "john@example.com", Age: 30}
result := db.Create(&user)
fmt.Println(user.ID)
fmt.Println(result.Error)
fmt.Println(result.RowsAffected)

// Batch insert
users := []User{{Name: "A"}, {Name: "B"}}
db.Create(&users)

Read

// Get first
var user User
db.First(&user)
db.First(&user, 10) // WHERE id = 10
db.First(&user, "name = ?", "John")

// Get all
var users []User
db.Find(&users)

// Conditions
db.Where("age > ?", 18).Find(&users)
db.Where("name LIKE ?", "%oh%").Find(&users)
db.Where(&User{Name: "John", Age: 30}).Find(&users)

// Order, Limit, Offset
db.Order("age desc").Limit(10).Offset(0).Find(&users)

Update

// Update single field
db.Model(&user).Update("age", 31)

// Update multiple fields
db.Model(&user).Updates(User{Age: 31, Name: "Jane"})
db.Model(&user).Updates(map[string]interface{}{"age": 31})

// Update all
db.Model(&User{}).Where("age < ?", 18).Update("active", false)

// Save (all fields)
user.Age = 31
db.Save(&user)

Delete

db.Delete(&user, 10)
db.Where("age < ?", 18).Delete(&User{})

// Soft delete (với gorm.Model)
db.Delete(&user) // sets deleted_at

// Permanent
db.Unscoped().Delete(&user)

Relations

Has Many

var user User
db.Preload("Posts").First(&user, 1)

Belongs To

var post Post
db.Preload("User").First(&post, 1)

Many to Many

type Student struct {
    gorm.Model
    Name    string
    Courses []Course `gorm:"many2many:student_courses;"`
}

type Course struct {
    gorm.Model
    Name     string
    Students []Student `gorm:"many2many:student_courses;"`
}

Transactions

err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&User{Name: "A"}).Error; err != nil {
        return err
    }
    if err := tx.Create(&Post{Title: "Post"}).Error; err != nil {
        return err
    }
    return nil
})

Testing

Table-driven tests

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, 1, 0},
        {"zero", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.expected)
            }
        })
    }
}

Testify

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestUserService(t *testing.T) {
    user, err := service.Create("John", "john@example.com")
    require.NoError(t, err)
    assert.Equal(t, "John", user.Name)
    assert.NotZero(t, user.ID)
}

In-memory database testing

import "gorm.io/driver/sqlite"

func setupTestDB(t *testing.T) *gorm.DB {
    db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
    require.NoError(t, err)
    db.AutoMigrate(&User{})
    return db
}

func TestCreateUser(t *testing.T) {
    db := setupTestDB(t)
    repo := NewUserRepository(db)

    user, err := repo.Create(User{Name: "John", Email: "j@e.com"})
    require.NoError(t, err)
    assert.NotZero(t, user.ID)
}

Mocking

type MockUserRepository struct {
    mock.Mock
}

func (m *MockUserRepository) FindByID(id uint) (*User, error) {
    args := m.Called(id)
    return args.Get(0).(*User), args.Error(1)
}

Bài tập thực hành

Hãy viết tests cho REST API với GORM!

📝 Bài tập (1)

  1. Implement CRUD operations và viết tests