使用Golang和Gin-Gonic进行单元测试的最佳实践
使用Golang和Gin-Gonic进行单元测试的最佳实践 以下是我的控制器,它使用了 Gin 上下文,但当我进行单元测试时,所有测试都失败了。
package controllers
import "github.com/gin-gonic/gin"
// HealthCheck godoc
// @Summary Show the status of server.
// @Description get the status of server.
// @Tags HealthCheck
// @Accept */*
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Router /healthcheck [get]
func HealthCheck(c *gin.Context) {
res := map[string]interface{}{
"data": "Server is up and running",
}
c.JSON(200, gin.H{
"data": res,
})
}
// @BasePath /api/v1
// PingExample godoc
// @Summary ping example
// @Schemes
// @Description do ping
// @Tags Ping
// @Accept json
// @Produce json
// @Success 200 {string} Pong
// @Router /ping [get]
func Ping(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
}
我的单元测试
package main
import (
"go-jwwt/initializers"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestHealthCheck(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.Default()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/healthcheck", nil)
router.ServeHTTP(w, req)
//Assertion
assert.Equal(t, http.StatusOK, w.Code)
//assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "Server is up and running", w.Body.String())
}
func TestPing(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.Default()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
router.ServeHTTP(w, req)
//Assertion
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "pong", w.Body.String())
}
文件 main.go
func main() {
r := gin.Default()
v1 := r.Group(os.Getenv("API_DEFAULT"))
{
v1.GET("/healthcheck", controllers.HealthCheck)
v1.GET("/ping", controllers.Ping)
}
r.Run() // listen and serve on 0.0.0.0:8080
}
在我的单元测试中遗漏了什么?
更多关于使用Golang和Gin-Gonic进行单元测试的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你是遇到了运行测试时的问题,还是在询问测试是否还应检查其他内容?
更多关于使用Golang和Gin-Gonic进行单元测试的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
感谢你发布这个错误;我现在明白了。
第一个问题是你没有告诉路由器关于你的 HealthCheck 和 Ping 控制器,所以 Gin 执行了默认行为,返回了 404。我以前从未使用过 Gin,但我在他们的网站上看了看,似乎你需要用路由器的 GET 函数来配置这些路由:
// ...
router := gin.Default()
router.GET("/healthcheck", controllers.HealthCheck)
router.GET("/ping", controllers.Ping)
这样就能解决 404 问题了。
是的:我遇到了一个错误
=== RUN TestHealthCheck
[GIN] 2022/07/30 - 15:42:17 | 404 | 0s | | GET ""/api/v1/healthcheck"
main_test.go:29:
Error Trace: myPath\go-jwt\main_test.go:29
Error: Not equal:
expected: 200
actual : 404
Test: TestHealthCheck
main_test.go:31:
Error Trace: myPath\go-jwt\main_test.go:31
Error: Not equal:
expected: "Server is up and running"
actual : "404 page not found"
Diff:
--- Expected
+++ Actual
@@ -1 +1 @@
-Server is up and running
+404 page not found
Test: TestHealthCheck
--- FAIL: TestHealthCheck (0.00s)
=== RUN TestPing
[GIN] 2022/07/30 - 15:42:17 | 404 | 0s | | GET "/api/v1/ping"
main_test.go:49:
Error Trace: myPath\go-jwt\main_test.go:49
Error: Not equal:
expected: 200
actual : 404
Test: TestPing
main_test.go:50:
Error Trace: myPath\go-jwt\main_test.go:50
Error: Not equal:
expected: "pong"
actual : "404 page not found"
Diff:
--- Expected
+++ Actual
@@ -1 +1 @@
-pong
+404 page not found
Test: TestPing
--- FAIL: TestPing (0.00s)
FAIL
当我运行 main.go 时,这些端点响应正常,这是否存在上下文问题? 我的测试中是否遗漏了什么?
你的测试失败是因为没有正确注册路由。在测试中创建了新的 gin.Default() 路由器,但没有注册控制器中定义的路由。以下是修正后的单元测试:
package controllers
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestHealthCheck(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.Default()
// 注册路由
router.GET("/healthcheck", HealthCheck)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/healthcheck", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"data":{"data":"Server is up and running"}}`, w.Body.String())
}
func TestPing(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.Default()
// 注册路由
router.GET("/ping", Ping)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"message":"pong"}`, w.Body.String())
}
如果你需要测试带分组路由的完整路由配置,可以这样写:
func TestHealthCheckWithGroup(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.Default()
// 模拟 main.go 中的路由配置
v1 := router.Group("/api/v1")
{
v1.GET("/healthcheck", HealthCheck)
v1.GET("/ping", Ping)
}
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/api/v1/healthcheck", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"data":{"data":"Server is up and running"}}`, w.Body.String())
}
func TestPingWithGroup(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.Default()
v1 := router.Group("/api/v1")
{
v1.GET("/healthcheck", HealthCheck)
v1.GET("/ping", Ping)
}
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/api/v1/ping", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"message":"pong"}`, w.Body.String())
}
对于更复杂的控制器测试,可以使用 gin.CreateTestContext:
func TestHealthCheckDirect(t *testing.T) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
// 模拟请求
c.Request = httptest.NewRequest(http.MethodGet, "/healthcheck", nil)
// 直接调用控制器函数
HealthCheck(c)
assert.Equal(t, http.StatusOK, w.Code)
assert.JSONEq(t, `{"data":{"data":"Server is up and running"}}`, w.Body.String())
}
主要问题是你没有在测试路由器中注册控制器函数的路由。在 Gin 的单元测试中,需要显式地将路由处理函数注册到测试路由器上,就像在 main.go 中做的那样。


