バックエンドのテスト
https://www.udemy.com/course/introduction-to-testing-in-go-golang/
を参考に実装しつつまとめていく
ハンドラのテスト
流れ
- requestにデータを詰める
- ハンドラを実行する
- 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の返り値を決め打ちでチェックしてる箇所は修正が必要になります。