0
0

TL; DR

はじめに

go-vcrはHTTPリクエストを行う関数のテストに使用するライブラリです。

初回テストでは実際にAPIリクエストを発生させ、リクエスト、レスポンスの組み合わせをカセット(実体はyamlファイル)に保存します。
2回目以降はクライアントがカセットをもとにレスポンスを返すため、実際にはリクエストが発生しません1

これにより、

  • 実際のAPIリクエスト、レスポンスでテストできる
  • リクエストを発生させずにテストできる

を両立することができます。

テスト実行時に対象サーバーへの疎通性やリクエスト負荷等を気にする必要はありません。また、冪等性のないリクエストが含まれていても繰り返し実行可能です。

レスポンスの加工

ところで、テストでは実際のレスポンスをそのまま使うと都合が悪い場合もあります。

  • 認証情報等コミットしたくない情報がカセットに載ってしまう
  • 実際のリクエストでは発生しなかった特殊なエラーレスポンスに対応する異常系をテストしたい

記録したカセットを手で書き直すことでも対応可能ですが、vcrの hooks を使えばより手軽に修正ができます。

hooksを使用

hooksは、リクエストやレスポンスをカセット(yamlファイル)に保存する前に加工する関数です。

公式リポジトリのREADMEより抜粋
// Authorizationヘッダを消すhook
hook := func(i *cassette.Interaction) error {
	delete(i.Request.Headers, "Authorization")
	return nil
}
// hookを追加し、レスポンスを受け取った後に適用されるようにする
r.AddHook(hook, recorder.AfterCaptureHook)

実例:httpbinのIPアドレス取得API

今回は実例として https://httpbin.org/ の IPアドレス取得APIを使用します。

GET /ip は、リクエスト元の(グローバル)IPアドレスを返します。

# この端末のグローバルIPアドレスが返ってくる(以下はxで伏字にしている)
$ curl httpbin.org/ip
{
  "origin": "xxx.xxx.xxx.xxx"
}

このAPIを呼び出す関数をgo-vcrを使用してテストする場合

  • セキュリティ上IPアドレス(=開発環境のグローバルIPアドレス)を含むカセットをコミットしたくない

という問題があります。そこで、hookを使ってIPアドレスをローカルホストに置き換えます2

まずはhookを作ります。パスが /ip に一致したとき、レスポンスボディで 127.0.0.1 を返すようにします3

func patchGetIPResponse(i *cassette.Interaction) error {
	if strings.Contains(i.Request.URL, "/ip") {
		i.Response.Body = `{"origin": "127.0.0.1"}`
	}

	return nil
}

そして、recorder生成時にこのhookを追加します。

func runRecorder(cassette string, hooks []func(*cassette.Interaction) error) (*recorder.Recorder, error) {
	r, err := recorder.New(cassette)

	// 受け取ったhookをすべて追加
	for _, hook := range hooks {
		r.AddHook(hook, recorder.AfterCaptureHook)
	}

	return r, err
}
テスト本体
func TestShowIPAddress(t *testing.T) {
	// 上記のhooksを指定
	hooks := []func(*cassette.Interaction) error{
		patchGetIPResponse,
	}

	r, err := runRecorder("testdata/get_ip", hooks)
	if err != nil {
		t.Fatal(err)
	}
	defer r.Stop()

	var out bytes.Buffer
	err = ShowIPAddress(&out, r.GetDefaultClient())
	if err != nil {
		t.Error(err)
	}

	// レスポンスを書き換えてからカセットに記録しているため、assertionも `127.0.0.1` を使用した内容になる
	expected := "IP Address: 127.0.0.1"
	actual := out.String()

	if expected != actual {
		t.Errorf("%q != %q", expected, actual)
	}
}

実装側も含めたコード全体については以下のリポジトリをご覧ください。

  1. モード変更により「常に上書き」「記録しない」等の動作も可能です。

  2. ローカルホストでなくても良いのですが、誤ってリクエストしても問題ないIPアドレスにするのが安全です。

  3. テストケースが多い場合は、HTTPメソッドやステータスコード等でも絞りこんだほうが良いと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0