これが私がやろうとしていることです:
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))
}
_
これにより、パスで指定されたURLパラメータをエコーするポート_8080
_でリッスンする基本的なhttpサーバーが作成されます。したがって、_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)
を偽造する方法ですか?私はいくつかの議論 here を見つけましたが、提案された解決策はもはや機能しません。提案された解決策は:
_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
_を3番目のパラメーターとして受け入れるようにコードを変更することです。その他のソリューションには、実際にサーバーを起動してリクエストを作成し、サーバーに直接送信することが含まれます。私の意見では、これは単体テストの目的を無効にし、それらを本質的に機能テストに変えます。
リンクされたスレッドが2013年のものであることを考慮してください。他のオプションはありますか?
[〜#〜]編集[〜#〜]
だから私は_gorilla/mux
_ソースコードを読みました、そして_mux.go
_に従って関数mux.Vars()
は次のように定義されています here :
_// 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
here として定義されます。したがって、基本的に、キー値は_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で値を比較するときにtypeが役割を果たすため、int(0) != contextKey(0)
となります。
ゴリラマルチプレクサやコンテキストをだまして値を返すようにする方法はわかりません。
そうは言っても、これをテストするいくつかの方法が頭に浮かびます(以下のコードはテストされていないことに注意してください。ここに直接入力したため、いくつかの愚かなエラーが発生する可能性があります)。
サーバーを実行する代わりに、テストでgorilla muxルーターを使用します。このシナリオでは、ListenAndServe
に渡すルーターが1つありますが、同じルーターインスタンスをテストで使用してServeHTTP
を呼び出すこともできます。ルーターがコンテキスト値の設定を処理し、ハンドラーで使用できるようになります。
_func Router() *mux.Router {
r := mux.Router()
r.HandleFunc("/employees/{1}", GetRequest)
(...)
return r
}
_
メイン関数のどこかに、次のようなことをします:
_http.Handle("/", Router())
_
そしてあなたのテストではあなたがすることができます:
_func TestGetRequest(t *testing.T) {
r := http.NewRequest("GET", "employees/1", nil)
w := httptest.NewRecorder()
Router().ServeHTTP(w, r)
// assertions
}
_
ハンドラーをラップして、URLパラメーターを3番目の引数として受け入れ、ラッパーが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を優先します。
gorilla/mux
は、テスト目的で SetURLVars
関数を提供します。これを使用して、モックvars
を挿入できます。
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())
}
Golangでは、テストに対するアプローチが少し異なります。
私はあなたのlibコードを少し書き直します:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
startServer()
}
func startServer() {
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))
}
そしてここにそれのためのテストがあります:
package main
import (
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestGetRequest(t *testing.T) {
go startServer()
client := &http.Client{
Timeout: 1 * time.Second,
}
r, _ := http.NewRequest("GET", "http://localhost:8080/test/abcd", nil)
resp, err := client.Do(r)
if err != nil {
panic(err)
}
assert.Equal(t, http.StatusOK, resp.StatusCode)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
assert.Equal(t, []byte("abcd"), body)
}
私はこれがより良いアプローチだと思います-リスナーを開始/停止するのが非常に簡単なので、あなたが書いたものを本当にテストしています!
次のヘルパー関数を使用して、単体テストからハンドラーを呼び出します。
func InvokeHandler(handler http.Handler, routePath string,
w http.ResponseWriter, r *http.Request) {
// Add a new sub-path for each invocation since
// we cannot (easily) remove old handler
invokeCount++
router := mux.NewRouter()
http.Handle(fmt.Sprintf("/%d", invokeCount), router)
router.Path(routePath).Handler(handler)
// Modify the request to add "/%d" to the request-URL
r.URL.RawPath = fmt.Sprintf("/%d%s", invokeCount, r.URL.RawPath)
router.ServeHTTP(w, r)
}
HTTPハンドラを登録解除する(簡単な)方法がないため、同じルートに対するhttp.Handle
への複数の呼び出しは失敗します。したがって、この関数は新しいルート(/1
または/2
など)を追加して、パスが一意であることを確認します。この魔法は、同じプロセスの複数の単体テストで関数を使用するために必要です。
GetRequest
-関数をテストするには:
func TestGetRequest(t *testing.T) {
t.Parallel()
r, _ := http.NewRequest("GET", "/test/abcd", nil)
w := httptest.NewRecorder()
InvokeHandler(http.HandlerFunc(GetRequest), "/test/{mystring}", w, r)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, []byte("abcd"), w.Body.Bytes())
}
問題は、変数を設定できないことです。
var r *http.Request
var key, value string
// runtime panic, map not initialized
mux.Vars(r)[key] = value
解決策は、テストごとに新しいルーターを作成することです。
// api/route.go
package api
import (
"net/http"
"github.com/gorilla/mux"
)
type Route struct {
http.Handler
Method string
Path string
}
func (route *Route) Test(w http.ResponseWriter, r *http.Request) {
m := mux.NewRouter()
m.Handle(route.Path, route).Methods(route.Method)
m.ServeHTTP(w, r)
}
ハンドラファイル内。
// api/employees/show.go
package employees
import (
"github.com/gorilla/mux"
)
func Show(db *sql.DB) *api.Route {
h := func(w http.ResponseWriter, r http.Request) {
username := mux.Vars(r)["username"]
// .. etc ..
}
return &api.Route{
Method: "GET",
Path: "/employees/{username}",
// Maybe apply middleware too, who knows.
Handler: http.HandlerFunc(h),
}
}
あなたのテストで。
// api/employees/show_test.go
package employees
import (
"testing"
)
func TestShow(t *testing.T) {
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/employees/ajcodez", nil)
Show(db).Test(w, r)
}
*api.Route
は、http.Handler
が必要な場所であればどこでも使用できます。