当前位置:  开发笔记 > 编程语言 > 正文

对使用gorilla/mux URL参数的函数进行单元测试

如何解决《对使用gorilla/muxURL参数的函数进行单元测试》经验,为你挑选了2个好方法。

这是我正在尝试做的事情:

main.go

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

func main() {
    mainRouter := mux.NewRouter().StrictSlash(true)
    mainRouter.HandleFunc("/test/{mystring}", GetRequest).Name("/test/{mystring}").Methods("GET")
    http.Handle("/", mainRouter)

    err := http.ListenAndServe(":8080", mainRouter)
    if err != nil {
        fmt.Println("Something is wrong : " + err.Error())
    }
}

func GetRequest(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    myString := vars["mystring"]

    w.WriteHeader(http.StatusOK)
    w.Header().Set("Content-Type", "text/plain")
    w.Write([]byte(myString))
}

这将创建一个基于端口的基本http服务器,8080该服务器回显路径中给出的URL参数.因此,http://localhost:8080/test/abcd它将回写包含abcd在响应正文中的响应.

GetRequest()函数的单元测试在main_test.go中:

package main

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/gorilla/context"
    "github.com/stretchr/testify/assert"
)

func TestGetRequest(t *testing.T) {
    t.Parallel()

    r, _ := http.NewRequest("GET", "/test/abcd", nil)
    w := httptest.NewRecorder()

    //Hack to try to fake gorilla/mux vars
    vars := map[string]string{
        "mystring": "abcd",
    }
    context.Set(r, 0, vars)

    GetRequest(w, r)

    assert.Equal(t, http.StatusOK, w.Code)
    assert.Equal(t, []byte("abcd"), w.Body.Bytes())
}

测试结果如下:

--- FAIL: TestGetRequest (0.00s)
    assertions.go:203: 

    Error Trace:    main_test.go:27

    Error:      Not equal: []byte{0x61, 0x62, 0x63, 0x64} (expected)
                    != []byte(nil) (actual)

            Diff:
            --- Expected
            +++ Actual
            @@ -1,4 +1,2 @@
            -([]uint8) (len=4 cap=8) {
            - 00000000  61 62 63 64                                       |abcd|
            -}
            +([]uint8) 


FAIL
FAIL    command-line-arguments  0.045s

问题是如何伪造mux.Vars(r)单元测试?我在这里找到了一些讨论,但建议的解决方案不再适用.建议的解决方案是:

func buildRequest(method string, url string, doctype uint32, docid uint32) *http.Request {
    req, _ := http.NewRequest(method, url, nil)
    req.ParseForm()
    var vars = map[string]string{
        "doctype": strconv.FormatUint(uint64(doctype), 10),
        "docid":   strconv.FormatUint(uint64(docid), 10),
    }
    context.DefaultContext.Set(req, mux.ContextKey(0), vars) // mux.ContextKey exported
    return req
}

此解决方案不起作用context.DefaultContext,mux.ContextKey因此不再存在.

另一个建议的解决方案是更改代码,以便请求函数也接受map[string]string第三个参数.其他解决方案包括实际启动服务器并构建请求并将其直接发送到服务器.在我看来,这将破坏单元测试的目的,将它们基本上转变为功能测试.

考虑到链接线程来自2013年的事实.还有其他选择吗?

编辑

所以我已经阅读了gorilla/mux源代码,并根据mux.go函数在这里mux.Vars()定义如下:

// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
    if rv := context.Get(r, varsKey); rv != nil {
        return rv.(map[string]string)
    }
    return nil
}

varsKey被定义为iota 在这里.基本上,关键值是0.我写了一个小测试应用来检查这个: main.go

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/gorilla/context"
)

func main() {
    r, _ := http.NewRequest("GET", "/test/abcd", nil)
    vars := map[string]string{
        "mystring": "abcd",
    }
    context.Set(r, 0, vars)
    what := Vars(r)

    for key, value := range what {
        fmt.Println("Key:", key, "Value:", value)
    }

    what2 := mux.Vars(r)
    fmt.Println(what2)

    for key, value := range what2 {
        fmt.Println("Key:", key, "Value:", value)
    }

}

func Vars(r *http.Request) map[string]string {
    if rv := context.Get(r, 0); rv != nil {
        return rv.(map[string]string)
    }
    return nil
}

运行时,输出:

Key: mystring Value: abcd
map[]

这让我想知道为什么测试不起作用以及为什么直接调用mux.Vars不起作用.



1> del-boy..:

麻烦的是,即使您使用0值作为设置上下文值,它也不是mux.Vars()读取的值.mux.Vars()正在使用varsKey(如你所见)哪种类型contextKey而不是int.

当然,contextKey定义为:

type contextKey int

这意味着它有int作为底层对象,但是在比较go中的值时类型会起作用,所以int(0) != contextKey(0).

我不知道如何欺骗大猩猩多路复用器或上下文来返回你的价值观.


话虽如此,我想到了几种测试方法(注意下面的代码是未经测试的,我在这里直接输入,所以可能会有一些愚蠢的错误):

    正如有人建议的那样,运行服务器并向其发送HTTP请求.

    而不是运行服务器,只需在测试中使用gorilla mux Router.在这种情况下,您将传递一个路由器ListenAndServe,但您也可以在测试中使用相同的路由器实例并对其进行调用ServeHTTP.路由器将负责设置上下文值,它们将在您的处理程序中可用.

    func Router() *mux.Router {
        r := mux.Router()
        r.HandleFunc("/employees/{1}", GetRequest)
        (...)
        return r 
    }
    

    在main函数的某个地方,你会做这样的事情:

    http.Handle("/", Router())
    

    在你的测试中你可以做到:

    func TestGetRequest(t *testing.T) {
        r := http.NewRequest("GET", "employees/1", nil)
        w := httptest.NewRecorder()
    
        Router().ServeHTTP(w, r)
        // assertions
    }
    

    包装处理程序,以便它们接受URL参数作为第三个参数,包装器应调用mux.Vars()并将URL参数传递给处理程序.

    使用此解决方案,您的处理程序将具有签名:

    type VarsHandler func (w http.ResponseWriter, r *http.Request, vars map[string]string)
    

    你必须调整它的调用以符合http.Handler接口:

    func (vh VarsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
        vh(w, r, vars)
    }
    

    要注册处理程序,您将使用:

    func GetRequest(w http.ResponseWriter, r *http.Request, vars map[string]string) {
        // process request using vars
    }
    
    mainRouter := mux.NewRouter().StrictSlash(true)
    mainRouter.HandleFunc("/test/{mystring}", VarsHandler(GetRequest)).Name("/test/{mystring}").Methods("GET")
    

你使用哪一个是个人喜好的问题.就个人而言,我可能选择2或3,略微偏向3.



2> 小智..:

您需要将测试更改为:

func TestGetRequest(t *testing.T) {
    t.Parallel()

    r, _ := http.NewRequest("GET", "/test/abcd", nil)
    w := httptest.NewRecorder()

    //Hack to try to fake gorilla/mux vars
    vars := map[string]string{
        "mystring": "abcd",
    }

    // CHANGE THIS LINE!!!
    r = mux.SetURLVars(r, vars)

    GetRequest(w, r)

    assert.Equal(t, http.StatusOK, w.Code)
    assert.Equal(t, []byte("abcd"), w.Body.Bytes())
}

推荐阅读
sx-March23
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有