バックエンドのテスト

https://www.udemy.com/course/introduction-to-testing-in-go-golang/

を参考に実装しつつまとめていく

ハンドラのテスト

流れ

  1. requestにデータを詰める
  2. ハンドラを実行する
  3. responseからデータを取り出してチェックする

テンプレ

func Test_Example(t *testing.T) {
    //テストケースを定義する
    tests := []struct {
        name   string
        data   string
        status int
    }{
        {"test 1", "data1", http.StatusBadRequest},
        {"test 2", "data2", http.StatusOK},
    }
    for _, test := range tests {
        //テスト実行
        t.Run(test.name, func(t *testing.T) {
            //requestの作成
            r := httptest.NewRequest(http.MethodPost, "/test", nil)
            //responseの作成
            w := httptest.NewRecorder()
            //handlerの作成
            handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
            //実行
            handler.ServeHTTP(w, r)

            //responseがwに書き込まれるのでテストケースと比較
            want_status := test.status
            got_status := w.Result().StatusCode
            if got_status != want_status {
                t.Errorf("statusCode: got %d, want %d", got_status, want_status)
            }
            if want_status == http.StatusOK {
                //responseのチェック
            } else {
                //エラーのチェック
            }
        })
    }
}

requestにデータを詰める

//json
//Marshalなどで文字列化して、NewRequestの引数(body)に入れる
r := httptest.NewRequest(http.MethodPost, "/test", strings.NewReader(jsonString))
//context
//WithContextで渡す
ctx := context.WithValue(context.Background(),ContextKey, "value")
handler.ServeHTTP(w, r.WithContext(ctx))

ミドルウェアのテスト

func myMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        next.ServeHTTP(w, r)
    })
}

//適当なハンドラを作ってmiddlewareに渡す
    emptyHandler := http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {})
    handler := myMiddleware(emptyHandler)
        handler.ServeHTTP(w, r)
//middleware内でcontextにセットした値はハンドラ内から取り出してチェック
    emptyHandler := http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
             value := r.Context().Value(Key)
             if value ==nil{
                 t.Error("")
             }
    handler := myMiddleware(emptyHandler)
})

APIリクエストのテスト

書き途中

//サーバーを立てる
         ts = httptest.NewTLSServer(app.registerHandlers())
         defer ts.Close()
//request作成
    r := httptest.NewRequest(http.MethodGet, ts.URL+"/_chk", nil)
//エラー防止
    r.RequestURI = ""
//client作成, request実行
    res, err := ts.Client().Do(r)
         defer res.Body.Close()
        if res.StatusCode != http.StatusOK{
            t.Error("...")
        }

ハンドラのテストと同様にhttptest.NewRequestからrequestを作成できますが、エラーが発生するためRequestURIの上書きが必要でした。
http.Request RequestURI field when making request in go - Stack Overflow

DB絡みをどうするか

正解はわかりませんが、現在はテスト専用のDBコンテナに接続してテスト毎にDBをクリア(DROP TABLEしてマイグレーションし直す)しています。

既に感じているデメリットとして

  • テスト全体で一つのDBを共有しているため、-p 1で並列実行を止める必要がある
  • 複数パッケージでDBを使おうとすると、importの都合でDBをクリアする関数がアプリ側に漏れてしまう

リクエストからレスポンスまで一貫して挙動を確認できるのは良いのですが、将来的にはDBコンテナを使ったテストはモデル内で完結させて、その他のパッケージからモデルを利用する時はモックを返すようにすると良いのかもしれません。
そうすることで並列実行が可能になり、クリア関数をテストファイル内に留めておくことができます。(一長一短な感じしますが・・・)

書いた後に知りましたがDBのクリアに関してはテスト終了後にロールバックするという裏技があるようなので、まず地獄みたいな関数を無くそうと思います。

テスト毎にロールバックする方式にしたところスピードも上がり大体解決しました。
トランザクション | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

auto_incrementの値がロールバックされないようなので、IDの返り値を決め打ちでチェックしてる箇所は修正が必要になります。