这是我正在尝试做的事情:
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
不起作用.
麻烦的是,即使您使用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.
您需要将测试更改为:
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()) }