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) }) 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)
}
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.- 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.