【Go】Goのテストに入門してみた! ~httptest編②~
はじめに
前回は「httptest」を見ていきました。
今回も「httptest」に入門します。
前回の記事はこちら!
この記事でわかること
- Goの標準パッケージである
httptestの概要や役割 -
httptestを使ったHTTPハンドラーのテスト方法 - POSTメソッドでハンドラーをテストする際の注意点
httptestとは?
httptest はGoに標準搭載されているパッケージです。
(net/http/httptest が該当します。)
httptest を使うことで、実際にHTTPサーバーを立ち上げることなく、HTTPハンドラーやWebアプリケーションのテストを簡単に行うことができます。
GoのhttptestでPOSTリクエストをテストする方法
今回はPOSTメソッドで httptest を使ったハンドラーのテストをしてみます。
フレームワークはEchoを使います。
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/labstack/echo/v4"
)
func postUserHandler(c echo.Context) error {
type Req struct {
Name string `json:"name"`
}
var req Req
if err := c.Bind(&req); err != nil {
fmt.Println(err)
return c.JSON(http.StatusBadRequest, map[string]string{"error":"invalid body"})
}
return c.JSON(http.StatusCreated, nil)
}
func TestPostUserHandler(t *testing.T) {
e := echo.New()
body := `{"name": "Bob"}`
req := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(body))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
if err := postUserHandler(c); err != nil {
t.Fatalf("handler returned error: %v", err)
}
if rec.Code != http.StatusCreated {
t.Errorf("expected status 200, got %d", rec.Code)
}
}
実行すると、以下のような結果が得られます。
# go test -v
=== RUN TestPostUserHandler
--- PASS: TestPostUserHandler (0.00s)
PASS
ok go-test-practice 0.310s
httptest + Echoハンドラーの実装解説
1. ハンドラーの定義
func postUserHandler(c echo.Context) error {
type Req struct {
Name string `json:"name"`
}
var req Req
if err := c.Bind(&req); err != nil {
fmt.Println(err)
return c.JSON(http.StatusBadRequest, map[string]string{"error":"invalid body"})
}
return c.JSON(http.StatusCreated, nil)
}
今回はリクエストボディを構造体フィールドにバインドするハンドラーとなっております。
本来であればこの後にデータの登録処理を行うところですが、今回は省略します。
2. Echoインスタンスの作成
e := echo.New()
前回同様、Echoのルーティングやハンドラーをテストするために必要なインスタンスを作成します。
3. テスト用のリクエストとレスポンスを作成
body := `{"name": "Bob"}`
req := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(body))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
今回大事になるのは NewRequest() です。
- 第一引数:POSTメソッドを指定
- 第二引数:エンドポイントを指定
- 第三引数:リクエストボディを指定
注意しなければいけないのは、そのままボディを渡してはいけないということです。
httptest.NewRequest のシグネチャはこうなっています:
func NewRequest(method, target string, body io.Reader) *http.Request
第三引数は io.Reader型を期待しており、string型のボディをそのまま渡せないようになっています。
文字列をリクエストボディとして使いたい場合は strings.NewReader(body) のように io.Reader に変換する必要があります。
そして、req.Header.Set() をContent-Typeヘッダーに application/json を設定するようにしましょう。
今回はJSON形式のデータを扱うよう実装していますが、デフォルトではヘッダーは空になっており、明示的に指定しないとバインドに失敗するので、ここも注意する必要があります。
httptest.NewRecorder() はハンドラーの出力を書き込むための擬似レスポンスライターです。
4. Echoコンテキストの作成
c := e.NewContext(req, rec)
Echo独自のコンテキストを作成します。
コンテキストにリクエストとレスポンスの疑似オブジェクトを入れることで、ハンドラーを実行することができます。
5. ハンドラーを実行
if err := postUserHandler(c); err != nil {
t.Fatalf("handler returned error: %v", err)
}
ハンドラーにコンテキストを渡し、ハンドラーを実行します。
もしエラーが返された場合はテストを失敗させ終了します。
6. レスポンスのステータスコードを確認
if rec.Code != http.StatusCreated {
t.Errorf("expected status 201, got %d", rec.Code)
}
期待するステータスコードとレスポンスのコードが一致するかをチェックします。
一致しない場合はエラーを返します。
まとめ
今回は「httptest」について学びました。
POSTメソッドの場合、NewRequest() にリクエストボディを渡すことで簡単に擬似リクエストを送ることができます。
第三引数には io.Reader型の値を渡す必要があるので注意しましょう。
また、今回はJSON形式でのデータのやり取りなので、Content-Typeヘッダーに application/json を明示的に指定しないとエラーになるので注意です。
次回も「環境変数のセット」に入門します!
Discussion