集成测试验证多个组件协同工作的正确性,通常涉及数据库、外部服务等。
使用测试数据库进行集成测试:
package main
import (
"database/sql"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
_ "github.com/go-sql-driver/mysql"
)
var testDB *sql.DB
func TestMain(m *testing.M) {
var err error
testDB, err = sql.Open("mysql", "root:@tcp(localhost:3306)/test_db")
if err != nil {
panic(err)
}
defer testDB.Close()
os.Exit(m.Run())
}
func setupTestDB(t *testing.T) {
testDB.Exec("TRUNCATE TABLE users")
}
func TestGetUsers(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
testDB.Exec("INSERT INTO users (name, email) VALUES (?, ?)", "张三", "test@example.com")
r := gin.New()
r.GET("/users", func(c *gin.Context) {
rows, _ := testDB.Query("SELECT id, name, email FROM users")
defer rows.Close()
var users []gin.H
for rows.Next() {
var id int
var name, email string
rows.Scan(&id, &name, &email)
users = append(users, gin.H{
"id": id,
"name": name,
"email": email,
})
}
c.JSON(http.StatusOK, users)
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
每个测试用事务包裹,测试后回滚:
func withTransaction(t *testing.T, fn func(tx *sql.Tx)) {
tx, err := testDB.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback()
fn(tx)
}
func TestCreateUser(t *testing.T) {
withTransaction(t, func(tx *sql.Tx) {
result, err := tx.Exec(
"INSERT INTO users (name, email) VALUES (?, ?)",
"张三", "test@example.com",
)
assert.NoError(t, err)
id, _ := result.LastInsertId()
assert.Greater(t, id, int64(0))
})
}
创建测试服务器:
func setupTestServer() *gin.Engine {
r := gin.New()
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
api := r.Group("/api")
api.Use(AuthMiddleware())
{
api.GET("/profile", getProfile)
api.PUT("/profile", updateProfile)
}
return r
}
func TestHealthEndpoint(t *testing.T) {
gin.SetMode(gin.TestMode)
r := setupTestServer()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/health", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
使用 mock 替代外部依赖:
type UserService interface {
GetUser(id string) (*User, error)
}
type MockUserService struct {
users map[string]*User
}
func (m *MockUserService) GetUser(id string) (*User, error) {
if user, ok := m.users[id]; ok {
return user, nil
}
return nil, errors.New("user not found")
}
func TestGetUserWithMock(t *testing.T) {
gin.SetMode(gin.TestMode)
mockService := &MockUserService{
users: map[string]*User{
"1": {ID: "1", Name: "张三"},
},
}
r := gin.New()
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
user, err := mockService.GetUser(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, user)
})
t.Run("用户存在", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/1", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
})
t.Run("用户不存在", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/999", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
})
}
func TestAuthenticatedEndpoint(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(AuthMiddleware())
r.GET("/protected", func(c *gin.Context) {
c.String(http.StatusOK, "ok")
})
t.Run("有效token", func(t *testing.T) {
token := generateTestToken()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", "Bearer "+token)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
})
t.Run("无效token", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", "Bearer invalid")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
}
func generateTestToken() string {
return "test-valid-token"
}
func TestFileUpload(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"filename": file.Filename,
"size": file.Size,
})
})
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, _ := writer.CreateFormFile("file", "test.txt")
part.Write([]byte("test content"))
writer.Close()
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/upload", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
集成测试验证组件间的协作。使用测试数据库或 mock 隔离外部依赖。事务回滚可以保持测试数据的干净。测试认证、文件上传等场景需要特殊处理请求构造。