2019年4月20日土曜日

net/http を httptest を使ってテストする方法

概要

net/http には net/http/httptest という標準パッケージがありこれを使えばテストを書くことができます
今回は net/http/httptest パッケージを使ってテストを書いてみました

環境

  • macOS 10.14.4
  • go 1.11.5

サンプルアプリ

  • vim $GOPATH/src/github.com/hawksnowlog/a/main.go
package main

import (
    "fmt"
    "net/http"
)

func HelloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("hello")
    w.Write([]byte("hello"))
}

func main() {
    http.HandleFunc("/", HelloHandler)
    http.ListenAndServe(":8080", nil)
}

/ にアクセスすると「hello」と返す簡単なアプリです
実際にアプリのハンドラに通っていることを確認するために fmt.Println を記載しています

基本的なテスト

ではテストを記載します
xxx_test.go というテストファイルを同一ディレクトリに置きましょう

  • vim $GOPATH/src/github.com/hawksnowlog/a/main_test.go
package main

import (
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestRoot(t *testing.T) {
    testserver := httptest.NewServer(http.HandlerFunc(HelloHandler))
    defer testserver.Close()
    res, err := http.Get(testserver.URL)
    if err != nil {
        t.Error(err)
    }
    hello, err := ioutil.ReadAll(res.Body)
    defer res.Body.Close()
    if err != nil {
        t.Error(err)
    }
    if res.StatusCode != 200 {
        t.Error("a response code is not 200")
    }
    if string(hello) != "hello" {
        t.Error("a response is not hello")
    }
}
  • go fmt github.com/hawksnowlog/a
  • go test github.com/hawksnowlog/a

でテスト出来ます

解説

まずはテストサーバを作成します
アプリ側で作成したハンドラを使って作成します

testserver := httptest.NewServer(http.HandlerFunc(HelloHandler))
defer testserver.Close()

ちゃんと Close するのを忘れないようにしましょう
作成したサーバに GET リクエストを送信するには http.Get を使います

res, err := http.Get(testserver.URL)

あとは受け取ったレスポンスをチェックすれば OK です

実際にアプリを動かしてテストしても OK

実は net/http/httptest で NewServer すると指定したハンドラを持つアプリがテスト用に立ち上がり LISTEN しています
上記の場合ただそこに http.Get しているだけです
なので httptest を使わずに以下のように書くこともできます

  • go build github.com/hawksnowlog/a
  • ./a

で普通にアプリを立ち上げたあとに、そのアプリ目掛けて http.Get すれば OK です

  • vim $GOPATH/src/github.com/hawksnowlog/a/main_test.go
package main

import (
    "io/ioutil"
    "net/http"
    "testing"
)

func TestRoot(t *testing.T) {
    res, err := http.Get("http://localhost:8080")
    if err != nil {
        t.Error(err)
    }
    hello, err := ioutil.ReadAll(res.Body)
    defer res.Body.Close()
    if err != nil {
        t.Error(err)
    }
    if res.StatusCode != 200 {
        t.Error("a response code is not 200")
    }
    if string(hello) != "hello" {
        t.Error("a response is not hello")
    }
}
  • go test github.com/hawksnowlog/a

ハンドラを mock するには

ハンドラを mock したいのであれば単純にアプリのハンドラを使わずにテスト側でハンドラを定義すれば OK です

  • vim $GOPATH/src/github.com/hawksnowlog/a/main_test.go
package main

import (
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "testing"
)

func mockHelloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("mock hello"))
}

func TestRoot(t *testing.T) {
    testserver := httptest.NewServer(http.HandlerFunc(mockHelloHandler))
    defer testserver.Close()
    res, err := http.Get(testserver.URL)
    if err != nil {
        t.Error(err)
    }
    hello, err := ioutil.ReadAll(res.Body)
    defer res.Body.Close()
    if err != nil {
        t.Error(err)
    }
    if res.StatusCode != 200 {
        t.Error("a response code is not 200")
    }
    if string(hello) != "mock hello" {
        t.Error("a response is not mock hello")
    }
}
  • go test github.com/hawksnowlog/a

ハンドラ内の特定の処理だけ mock したい場合には面倒ですがその部分だけ mock したハンドラを定義すれば OK です
もしくは gomock というパッケージがあるのでこれを使って特定のメソッドを mock するという方法もあります

最後に

net/http/httpclient を使って net/http をテストしてみました
基本的にはテストしたいハンドラ指定したり mock してテストします
これだけでも基本的なテストは十分にできるかなと思います

0 件のコメント:

コメントを投稿