使用大猩猩/ mux URL参数的功能的单元测试

塞巴斯蒂安·劳伦(Sebastian-Laurenţiu)Plesciuc:

这是我想做的事情:

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) <nil>


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不再存在。

另一个建议的解决方案是更改您的代码,以便请求函数也接受a 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不起作用。

del-boy:

问题是,即使当您将其0用作值来设置上下文值时,它也不会mux.Vars()读取相同的值mux.Vars()正在使用varsKey(如您所见)的类型contextKey而不是int

当然,contextKey定义为:

type contextKey int

这意味着它具有int作为基础对象,但在比较go中的值时,type起作用int(0) != contextKey(0)

我看不到如何欺骗大猩猩多态或上下文返回您的值。


话虽这么说,想到了几种测试方法(请注意,以下代码未经测试,我在这里直接输入了代码,因此可能存在一些愚蠢的错误):

  1. 正如有人建议的那样,运行服务器并向其发送HTTP请求。
  2. 无需运行服务器,只需在测试中使用gorilla mux路由器即可。在这种情况下,您将拥有一个传递给的路由器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
    }
    
  3. 包装处理程序,以便它们接受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略有偏爱。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章