Gin 应用的单元测试主要使用
net/http/httptest包来模拟 HTTP 请求。
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestPingRoute(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/ping", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"message":"pong"}`, w.Body.String())
}
func TestCreateUser(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.POST("/users", func(c *gin.Context) {
var user struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
}
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"id": 1,
"name": user.Name,
"email": user.Email,
})
})
body := `{"name":"张三","email":"zhangsan@example.com"}`
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
}
func TestCreateUserValidation(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.POST("/users", createUserHandler)
body := `{"name":"","email":"invalid"}`
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestGetUser(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"id": id,
"name": "用户" + id,
})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/users/123", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"id":"123","name":"用户123"}`, w.Body.String())
}
func TestSearch(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/search", func(c *gin.Context) {
q := c.Query("q")
page := c.DefaultQuery("page", "1")
c.JSON(http.StatusOK, gin.H{
"query": q,
"page": page,
})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/search?q=golang&page=2", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"query":"golang","page":"2"}`, w.Body.String())
}
func TestAuthMiddleware(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "未授权",
})
return
}
c.Next()
})
r.GET("/protected", func(c *gin.Context) {
c.String(http.StatusOK, "ok")
})
t.Run("无token", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/protected", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("有token", func(t *testing.T) {
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)
})
}
func performRequest(r http.Handler, method, path string, body io.Reader) *httptest.ResponseRecorder {
req, _ := http.NewRequest(method, path, body)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
func TestWithHelper(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/hello", func(c *gin.Context) {
c.String(http.StatusOK, "hello")
})
w := performRequest(r, "GET", "/hello", nil)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "hello", w.Body.String())
}
func TestCalculator(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.GET("/calc", func(c *gin.Context) {
a, _ := strconv.Atoi(c.Query("a"))
b, _ := strconv.Atoi(c.Query("b"))
op := c.Query("op")
var result int
switch op {
case "add":
result = a + b
case "sub":
result = a - b
case "mul":
result = a * b
}
c.JSON(http.StatusOK, gin.H{"result": result})
})
tests := []struct {
name string
query string
expected int
}{
{"加法", "a=1&b=2&op=add", 3},
{"减法", "a=5&b=3&op=sub", 2},
{"乘法", "a=4&b=3&op=mul", 12},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := performRequest(r, "GET", "/calc?"+tt.query, nil)
var response map[string]int
json.Unmarshal(w.Body.Bytes(), &response)
assert.Equal(t, tt.expected, response["result"])
})
}
}
Gin 的单元测试使用 httptest 包模拟 HTTP 请求。使用表格驱动测试可以覆盖多种场景。测试辅助函数可以减少重复代码。记得在测试中设置 gin.TestMode 避免不必要的日志输出。