🧪

【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を使います。

main_test.go
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. ハンドラーの定義

main_test.go
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インスタンスの作成

main_test.go
e := echo.New()

前回同様、Echoのルーティングやハンドラーをテストするために必要なインスタンスを作成します。

3. テスト用のリクエストとレスポンスを作成

main_test.go
body := `{"name": "Bob"}`
req := httptest.NewRequest(http.MethodPost, "/user", strings.NewReader(body))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()

今回大事になるのは NewRequest() です。

  1. 第一引数:POSTメソッドを指定
  2. 第二引数:エンドポイントを指定
  3. 第三引数:リクエストボディを指定

注意しなければいけないのは、そのままボディを渡してはいけないということです。
httptest.NewRequest のシグネチャはこうなっています:

httptest.go
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コンテキストの作成

main_test.go
c := e.NewContext(req, rec)

Echo独自のコンテキストを作成します。
コンテキストにリクエストとレスポンスの疑似オブジェクトを入れることで、ハンドラーを実行することができます。

5. ハンドラーを実行

main_test.go
if err := postUserHandler(c); err != nil {
    t.Fatalf("handler returned error: %v", err)
}

ハンドラーにコンテキストを渡し、ハンドラーを実行します。
もしエラーが返された場合はテストを失敗させ終了します。

6. レスポンスのステータスコードを確認

main_test.go
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