emahiro/b.log

勉強記録と書評とたまに長めの呟きを書きます

Use httpmock with fmt

Summary

golangのhttpのmocking packageである httpmockfmt packageを使ってstring型に変換したときにハマった話を書きます。

httpmock packageはこちら

github.com

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で文字列型にするときにはエスケープのことを考慮することが必要です。