emahiro/b.log

Drastically Repeat Yourself !!!!

Go の httptest で立てたサーバーにアクセスする

テストなどで実際にサーバーを立てずに、HTTP のリクエストをシミュレートしたいときに httptest を使いますが、この httptest で立てたダミーサーバーそのものアクセスする方法はないかを調べてみました。

Motivation

あるテストをメンテしていた時に httptest.NewServer で作成したダミーのサーバーに対してリクエストを送っているのですが、ダミーサーバーにうまく送信できずにテストが落ちる、と言うことを繰り返してました。

結果としては、httptest.NewServer に router を差し込んで、アプリケーションで定義してる routing をシミュレートできていなかったことが原因でした。
このとき、テスト対象のアプリの routing に依存しない独自の Hnalder を定義した httptest.NewServer で立てたサーバーに向けて、 HTTP のリクエストを送る方法を知りたいないと思ったのがこのエントリを書こうと思った動機です。

サンプルコード

httptest.Server を立てます。

httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("hello in test server."))
}))

NewServer の引数に router を指定してないのでこの状態で httptest.NewRequst でリクエストを生成してサーバーにアクセスすることはできません。

ではどうやってこの生成したサーバーにアクセスするのかを godoc と実際のコードを追いながら調べたところ httptest.Serverhttp.Server 型の Config と言う Field が存在しており、これが httptest.NewServer して立てたサーバーの本体のようです。
(このフィールドの用途はサーバーを立てた後に構成を変更するためのものらしいので、実際の用途とは少し違う使い方をすることになりそうです。)

実際に httptest.NewServer で立てたサーバーにアクセスするコードは以下のようになります。

func TestMain(m *testing.M) {
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Printf("request in test server. req: %+v", r)
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("hello in test server."))
    }))

    r := httptest.NewRequest(http.MethodGet, "/", nil)
    w := httptest.NewRecorder()
    ts.Config.Handler.ServeHTTP(w, r)
}

実際にテストを実行してみます。
ちなみに TestMain にした意図はテストをしたいわけではなくて、テストでサーバーを起動してアクセスすることが目的だからです。

go test -v . 
request in test server. req: &{Method:GET URL:/ Proto:HTTP/1.1 ProtoMajor:1 ProtoMinor:1 Header:map[] Body:{} GetBody:<nil> ContentLength:0 TransferEncoding:[] Close:false Host:example.com Form:map[] PostForm:map[] MultipartForm:<nil> Trailer:map[] RemoteAddr:192.0.2.1:1234 RequestURI:/ TLS:<nil> Cancel:<nil> Response:<nil> ctx:<nil>}
ok      github.com/emahiro/ilhttptest   0.550s

ちゃんと作成したサーバーにアクセスできてますね。

ユースケース

正直これを書いた後に、じゃあ実際どう言うケースでこのテストサーバーにアクセスできることが嬉しくなるのだろう?っと考えて「これだ!」と言うことは思いつきませんでした...。

以下のようなミドルウェアを実装して

func mw() func(http.Handler) http.Handler {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // middleware で何かしらの処理をする
            fmt.Printf("This is in middleware\n")
        })
    }
}

サーバーを作る時に事前に差し込んでおくとか考えましたけど、その場合には

h := mw()(http.NewServeMux())
r := httptest.NewRequest()
w := httptest.NewRecorder()
h.ServeHTTP(w,r)

とした方が便利ですしコード少なくて済みます。

テストサーバーに直でアクセスできる方が嬉しいケースがまだ足りませんが、httptest で立てたサーバーへのアクセス方法を知ることはできました。

今回書いたコードは以下にあげてます。

github.com