使用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

5 回复

你是遇到了运行测试时的问题,还是在询问测试是否还应检查其他内容?

更多关于使用Golang和Gin-Gonic进行单元测试的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢你发布这个错误;我现在明白了。

第一个问题是你没有告诉路由器关于你的 HealthCheckPing 控制器,所以 Gin 执行了默认行为,返回了 404。我以前从未使用过 Gin,但我在他们的网站上看了看,似乎你需要用路由器的 GET 函数来配置这些路由:

// ...
router := gin.Default()
router.GET("/healthcheck", controllers.HealthCheck)
router.GET("/ping", controllers.Ping)

这样就能解决 404 问题了。

我通过按照其单元测试的预期进行修改来解决这个问题:

package main

import (
	"go-jwwt/initializers"

	"net/http"
	"net/http/httptest"
	"testing"
	"github.com/stretchr/testify/assert"
	"github.com/gin-gonic/gin"
)

func TestHealthCheck(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := setUpRouter()
	w := httptest.NewRecorder()

	wanted :=`{"data":{"data":"Server is up and running"}}`

	req, _  := http.NewRequest(http.MethodGet, "/api/v1/healthcheck", nil)
	router.ServeHTTP(w, req)
	//Assertion
	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, wanted, w.Body.String())
}



func TestPing(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := setUpRouter()
	w := httptest.NewRecorder()

	wanted :=`{"message":"pong"}`

	req, _  := http.NewRequest(http.MethodGet, "/api/v1/ping", nil)
	router.ServeHTTP(w, req)
	//Assertion
	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, wanted, w.Body.String())
}

以及路由器函数

func setUpRouter()  *gin.Engine {
	r := gin.Default()
	v1 := r.Group(os.Getenv("API_DEFAULT"))
	{
		// Routes
		v1.GET("/healthcheck", controllers.HealthCheck)
		v1.GET("/ping", controllers.Ping)
	}

	return r 
}

是的:我遇到了一个错误

=== 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 中做的那样。

回到顶部