Summary
golangのhttpのmocking packageである httpmock
で fmt
packageを使ってstring型に変換したときにハマった話を書きます。
httpmock packageはこちら
How to Use
var resJson = ` { "id": 1, "name": "taro" } ` func TestHttpMock(t *testing.T){ RegisterResponder("GET", "https://sample.com/users/1", httpmock.NewStringResponder(200, resJson)) // some unit test }
httpmock
を使ってurlとmethodを指定するだけで、mockしたいリクエストのResponseを定義できる。
上記では /users/1
の内容に対して resJson
で指定したStringをresponseとして登録します。
ハマったところ
上記の別パターンで幾つかresponseの内容を追加でmockしたいとします。以下のようなパターンです
resJson1 = `{ "id": 1, "studentID": 123, "name": "taro", "age":20, "sex": "male" }` func TestHttpMock(t *testing.T){ RegisterResponder("GET", "https://sample.com/users?name=taro&studentID=123", httpmock.NewStringResponder(200, resJson)) // some unit test }
上記のようにクエリパラメータでURLを指定してリクエストのmockを用意するケースを考えます。
例えば以下のように、 studentID
が幾つかあって、複数のクエリ条件のもとmockするデータを用意するとき
resJson1 = `{ "id": 1, "studentID": 123, "name": "katsuo", "age":20, "sex": "male" }` resJson1 = `{ "id": 2, "studentID": 456, "name": "wakame", "age":18, "sex": "female" }` resJson1 = `{ "id": 3, "studentID": 678, "name": "tara", "age":10, "sex": "male" }` func TestHttpMock(t *testing.T){ studentIDs := []int64{123,456,789} for _, id := range studentIDs{ RegisterResponder("GET", fmt.Sprintf("https://sample.com/users?sex=male&studentID=%d", id), httpmock.NewStringResponder(200, resJson)) // some unit test } }
コードは適当ですが、こんなケースをしている、urlを動的にイテレーションでループさせて一括でユニットテストを書きたいみたいなシチュエーションがあると思います。
しかしこのコードは動きません。原因は 適切に特殊文字がエスケープされていない からです。
Mockの部分を若干修正したコードが下記です。
func TestHttpMock(t *testing.T){ studentIDs := []int64{123,456,789} for _, id := range studentIDs{ RegisterResponder("GET", fmt.Sprintf("https://sample.com/users?sex=male%26studentID=%d", id), httpmock.NewStringResponder(200, resJson)) // some unit test } }
&
の特殊文字を %26
にエスケープしました。
実はこれでもまだ動きません。
理由は、エスケープした結果の %26
の %
の部分が、format関数で指定されている %
をバッティングしてしまって、正確にクエリパラメータを読み込めてなかったからです。
そのため、エスケープした結果、ちゃんと%として使われる必要がある ということだったのです。goのfmtの標準パッケージの中に書いてます。(https://golang.org/pkg/fmt/#hdr-Printing)
fmt - The Go Programming Language
%% a literal percent sign; consumes no value
つまり、%%
-> %
の文字列として扱えます。なので下記になります。
func TestHttpMock(t *testing.T){ studentIDs := []int64{123,456,789} for _, id := range studentIDs{ RegisterResponder("GET", fmt.Sprintf("https://sample.com/users?sex=male%%26studentID=%d", id), httpmock.NewStringResponder(200, resJson)) // some unit test } }
これでMock完了です。
まとめ
httpmockでfmt.Sprintfで文字列型にするときにはエスケープのことを考慮することが必要です。