如何对 Go Gin 处理函数进行单元测试?

Hsn*_*Hsn 12 integration-testing unit-testing go go-gin

我有一个这样的控制器功能......

func GetMaterialByFilter(c *gin.Context) {

    queryParam := weldprogs.QueryParam{}
    c.BindQuery(&queryParam)
    materialByFilter, getErr := services.WeldprogService.GetMaterialByFilter(&queryParam)
    if getErr != nil {
        //TODO : Handle user creation error
        c.JSON(getErr.Status, getErr)
        return
    }
    c.JSON(http.StatusOK, materialByFilter)

}
Run Code Online (Sandbox Code Playgroud)

QueryParam 结构是这样的..

type QueryParam struct {
    Basematgroup_id []string `form:"basematgroup_id"`
    License_id      []string `form:"license_id"`
    Diameter_id     []string `form:"diameter_id"`
    Gasgroup_id     []string `form:"gasgroup_id"`
    Wiregroup_id    []string `form:"wiregroup_id"`
    Wiremat_id      []string `form:"wiremat_id"`
}
Run Code Online (Sandbox Code Playgroud)

我的测试功能是这样的..

func TestGetMaterialByFilter(t *testing.T) {
    w := httptest.NewRecorder()
    c, _ := gin.CreateTestContext(w)
    GetMaterialByFilter(c)
    assert.Equal(t, 200, w.Code) 

    var got gin.H
    err := json.Unmarshal(w.Body.Bytes(), &got)
    if err != nil {
        t.Fatal(err)
    }
    fmt.Println(got)
    assert.Equal(t, got, got) 
}
Run Code Online (Sandbox Code Playgroud)

运行此测试时出现以下错误

panic: runtime error: invalid memory address or nil pointer dereference [recovered]
        panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x97f626]
Run Code Online (Sandbox Code Playgroud)

但是当我注释掉控制器函数中的 c.BindQuery() 行时,它成功运行了我的测试函数。我在这里做错了什么?我可以以某种方式模拟 c.BindQuery 函数吗?

bla*_*een 15

要测试涉及 HTTP 请求的操作,您必须实际初始化*http.Request并将其设置为 Gin 上下文。要专门测试c.BindQuery它足以正确初始化请求URLURL.RawQuery

\n
func mockGin() (*gin.Context, *httptest.ResponseRecorder) {\n    w := httptest.NewRecorder()\n    c, _ := gin.CreateTestContext(w)\n\n    // test request, must instantiate a request first\n    req := &http.Request{\n        URL:    &url.URL{},\n        Header: make(http.Header), // if you need to test headers\n    }\n    // example: req.Header.Add("Accept", "application/json")\n\n    // request query\n    testQuery := weldprogs.QueryParam{/* init fields */}\n\n    q := req.URL.Query()\n    for _, s := range testQuery.Basematgroup_id {\n        q.Add("basematgroup_id", s)\n    }\n    // ... repeat for other fields as needed\n\n    // must set this, since under the hood c.BindQuery calls\n    // `req.URL.Query()`, which calls `ParseQuery(u.RawQuery)`\n    req.URL.RawQuery = q.Encode()\n    \n    // finally set the request to the gin context\n    c.Request = req\n\n    return c, w\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如果您需要模拟 JSON 绑定,请参阅此答案

\n
\n

services.WeldprogService.GetMaterialByFilter(&queryParam)无法按原样测试服务调用。为了可测试,它必须是(理想情况下)一个接口,并以某种方式注入为处理程序的依赖项。

\n

假设它已经是一个接口,为了使其可注入,您要么需要它作为处理程序参数 \xe2\x80\x94 但这迫使您更改处理程序 \xe2\x80\x94 的签名,或者将其设置为Gin 上下文值:

\n
func GetMaterialByFilter(c *gin.Context) {\n    //...\n    weldprogService := mustGetService(c)\n    materialByFilter, getErr := weldprogService.GetMaterialByFilter(&queryParam)\n    // ...\n}\n\nfunc mustGetService(c *gin.Context) services.WeldprogService {\n    svc, exists := c.Get("svc_context_key")\n    if !exists {\n        panic("service was not set")\n    }\n    return svc.(services.WeldprogService)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后你可以在单元测试中模拟它:

\n
type mockSvc struct {\n}\n\n// have \'mockSvc\' implement the interface \n\nfunc TestGetMaterialByFilter(t *testing.T) {\n    w := httptest.NewRecorder()\n    c, _ := gin.CreateTestContext(w)\n\n    // now you can set mockSvc into the test context\n    c.Set("svc_context_key", &mockSvc{})\n\n    GetMaterialByFilter(c)\n    // ... \n}\n
Run Code Online (Sandbox Code Playgroud)\n