GOLANG INTEGRATION TEST VỚI GIN, GORM, TESTIFY, MYSQL
Trong bài này chúng ta sẽ viết một chương trình integration test cơ bản cho ứng dụng Golang sử dụng các thư viện như Gin
, Gorm
, Testify
, và MySQL
(sử dụng giải pháp in-memory) bao gồm việc setup môi trường testing, định nghĩa các routes và handlers, và test chúng với một real database (mặc dù việc dùng MySQL
in-memory có thể cần đến giải pháp thay thế như sử dụng SQLite
ở chế độ in-memory để cho đơn giản hơn).
Dưới đây là ví dụ về cách thiết lập một chương trình integration test:
1. Dependencies:
Gin
: dùng để tạo HTTP server.Gorm
: dùng cho ORM để tương tác với cơ sở dữ liệu.Testify
: dùng để hỗ trợ kiểm tra (assertions).SQLite
in-memory: đóng vai trò thay thế cho MySQL trong quá trình kiểm thử.
2. Setup:
- Định nghĩa một model cơ bản và thiết lập Gorm.
- Tạo HTTP routes và handlers.
- Viết các bài test sử dụng
Testify
vàSQLite
như một in-memory database.
Dưới đây là ví dụ đầy đủ:
// main.go
package main
import (
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"net/http"
)
// User represents a simple user model.
type User struct {
ID uint `gorm:"primaryKey"`
Name string `json:"name"`
Email string `json:"email" gorm:"unique"`
}
// SetupRouter initializes the Gin engine with routes.
func SetupRouter(db *gorm.DB) *gin.Engine {
r := gin.Default()
// Inject the database into the handler
r.POST("/users", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, user)
})
r.GET("/users/:id", func(c *gin.Context) {
var user User
id := c.Param("id")
if err := db.First(&user, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
c.JSON(http.StatusOK, user)
})
r.PUT("/users/:id", func(c *gin.Context) {
var user User
id := c.Param("id")
if err := db.First(&user, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := db.Save(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
r.DELETE("/users/:id", func(c *gin.Context) {
id := c.Param("id")
if err := db.Delete(&User{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "User deleted"})
})
c.JSON(http.StatusOK, user)
})
return r
}
func main() {
// For production, use MySQL
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&User{})
r := SetupRouter(db)
r.Run(":8080")
}
Integration Test
// main_test.go
package main
import (
"bytes"
"encoding/json"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// SetupTestDB sets up an in-memory SQLite database for testing.
func SetupTestDB() *gorm.DB {
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
panic("failed to connect to the test database")
}
db.AutoMigrate(&User{})
return db
}
func TestCreateUser(t *testing.T) {
db := SetupTestDB()
r := SetupRouter(db)
// Create a new user.
user := User{Name: "John Doe", Email: "john@example.com"}
jsonValue, _ := json.Marshal(user)
req, _ := http.NewRequest("POST", "/users", bytes.NewBuffer(jsonValue))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
var createdUser User
json.Unmarshal(w.Body.Bytes(), &createdUser)
assert.Equal(t, "John Doe", createdUser.Name)
assert.Equal(t, "john@example.com", createdUser.Email)
}
func TestGetUser(t *testing.T) {
db := SetupTestDB()
r := SetupRouter(db)
// Insert a user into the in-memory database.
user := User{Name: "Jane Doe", Email: "jane@example.com"}
db.Create(&user)
// Make a GET request.
req, _ := http.NewRequest("GET", "/users/1", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var fetchedUser User
json.Unmarshal(w.Body.Bytes(), &fetchedUser)
assert.Equal(t, "Jane Doe", fetchedUser.Name)
assert.Equal(t, "jane@example.com", fetchedUser.Email)
}
func TestGetUserNotFound(t *testing.T) {
db := SetupTestDB()
r := SetupRouter(db)
// Make a GET request for a non-existent user.
req, _ := http.NewRequest("GET", "/users/999", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestUpdateUser(t *testing.T) {
db := SetupTestDB()
r := SetupRouter(db)
// Insert a user into the in-memory database.
user := User{Name: "Jane Doe", Email: "jane@example.com"}
db.Create(&user)
// Update user details.
updatedUser := User{Name: "Jane Smith", Email: "jane.smith@example.com"}
jsonValue, _ := json.Marshal(updatedUser)
req, _ := http.NewRequest("PUT", "/users/1", bytes.NewBuffer(jsonValue))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var fetchedUser User
json.Unmarshal(w.Body.Bytes(), &fetchedUser)
assert.Equal(t, "Jane Smith", fetchedUser.Name)
assert.Equal(t, "jane.smith@example.com", fetchedUser.Email)
}
func TestDeleteUser(t *testing.T) {
db := SetupTestDB()
r := SetupRouter(db)
// Insert a user into the in-memory database.
user := User{Name: "Jane Doe", Email: "jane@example.com"}
db.Create(&user)
// Delete the user.
req, _ := http.NewRequest("DELETE", "/users/1", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
// Verify that the user is deleted.
var fetchedUser User
err := db.First(&fetchedUser, user.ID).Error
assert.Error(t, err)
assert.Equal(t, gorm.ErrRecordNotFound, err)
}
Giải thích
main.go
:
- Định nghĩa một
User
struct và thiết lập các thao tác CRUD cơ bản sử dụngGin
. - Sử dụng
Gorm
để tương tác với cơ sở dữ liệu và tự động migrate bảngUser
. SetupRouter
cấu hình các HTTP endpoint.
main_test.go
:
SetupTestDB
thiết lập SQLite in-memory database cho việc testing.TestCreateUser
: Test việc tạo một user.TestGetUser
: Test việc lấy một user đã tồn tại.TestGetUserNotFound
: Test việc lấy một user không tồn tại.TestUpdateUser
: Test việc update một user đã tồn tại.TestDeleteUser
: Test việc delete một user đã tồn tại.- Sử dụng
httptest.NewRecorder
vàhttp.NewRequest
để giả lập các request và response HTTP. - Sử dụng
Testify
để kiểm tra, như test HTTP status code và xác minh phản hồi JSON.
Khởi chạy chương trình test
Để run test, sử dụng lệnh:
go test -v
Lưu ý
- SQLite for In-memory Testing: Ví dụ này sử dụng
SQLite
cho việc kiểm thử in-memory vìMySQL
không hỗ trợ chế độ in-memory vớiGorm
. Đối với các chương trình test cần phụ thuộc vào các tính năng cụ thể củaMySQL
, cân nhắc sử dụng giải pháp dựa trên Docker với một containerMySQL
. - Database Migrations: Luôn đảm bảo schema của cơ sở dữ liệu được cập nhật bằng cách sử dụng
AutoMigrate
trong các chương trình test. - Isolation: Mỗi hàm test sẽ khởi tạo một in-memory database mới, đảm bảo các hàm test không gây ảnh hưởng lẫn nhau.
Nếu bạn thấy bài viết này hữu ích, hãy cho mình biết bằng cách để lại 👍 hoặc bình luận!, hoặc nếu bạn nghĩ bài viết này có thể giúp ích cho ai đó, hãy thoải mái chia sẻ! Cảm ơn bạn rất nhiều! 😃
All rights reserved