Goのhttpリクエストのmockについて

Goでhttpリクエス

いくつか方法はあるようだが、

client := &http.Client{
    CheckRedirect: redirectPolicyFunc,
}

resp, err := client.Get("http://example.com")
// ...

req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("If-None-Match", `W/"wyzzy"`)
resp, err := client.Do(req)
// ...

こっちの書き方でクライアントを実装する(http.Clientを使用する)

実装コード

github.com

httpmockを使わない例

  • 以下のように実装できる(説明のためにちょっと修正)
type HttpClient interface {
    Do(req *http.Request) (*http.Response, error)
}

func NewContentUsecase(c HttpClient) *ContentUsecase {
    return &ContentUsecase{
        httpClient: c,
    }
}

type ContentUsecase struct {
    httpClient HttpClient
}

func (content ContentUsecase) GetContent() (string, error) {
    request, _ := http.NewRequest("GET", "https://google.com", nil)
    resp, _ := content.httpClient.Do(request)
    defer resp.Body.Close()
    byteArray, _ := ioutil.ReadAll(resp.Body)

    return string(byteArray), nil
}
  • キモとなるのはGetContentの引数に渡されてくるContentUsecase
  • このContentUsecaseHttpClientインターフェースを満たしており、それを引数で注入する
  • テスト時にはこの引数に渡すものをmockすれば良い

テスト(httpmockを使わない例)

type clientMock struct {
    HttpClient
    MockedDo func(req *http.Request) (*http.Response, error)
}

func (c *clientMock) Do(req *http.Request) (*http.Response, error) {
    return c.MockedDo(req)
}

func TestGetContent(t *testing.T) {
    t.Run("GetContent正常系", func(t *testing.T) {
        spyMocked := &clientMock{
            MockedDo: func(req *http.Request) (*http.Response, error) {
                return &http.Response{
                    StatusCode: http.StatusOK,
                    Body:       ioutil.NopCloser(bytes.NewBufferString("OK")),
                }, nil
            },
        }
        contentUsecase := NewContentUsecase(spyMocked)
        content, err := contentUsecase.GetContent()

        if err != nil {
            t.Errorf("err should be nil: %v", err)
        }

        if content != "OK" {
            t.Errorf("Returned Content invalid: %v", content)
        }
    })
}
  • HttpClientインターフェースの中身をMockする(Do関数を置き換える)
  • 実際のテストコードの中でDo関数が何を返すのかを実装してやれば良い(MockedDoを実装する)

httpmockを使う例

  • こっちのほうはとても簡単
  • でも、標準ライブラリのみで使える前者のほうがいいんではないかとか思ったりする

  • 一部プログラム変更してあります(エラーハンドリングなど)

func GetContentUseMock() (string, error) {
    request, _ := http.NewRequest("GET", "https://example.com", nil)
    client := &http.Client{}
    resp, _ := client.Do(request)
    defer resp.Body.Close()
    byteArray, _ := ioutil.ReadAll(resp.Body)

    return string(byteArray), nil
}

テスト

func TestGetContentUseMock(t *testing.T) {
    t.Run("GetContentUseMock正常系", func(t *testing.T) {
        httpmock.Activate()
        defer httpmock.DeactivateAndReset()

        httpmock.RegisterResponder("GET", "https://example.com",
            func(req *http.Request) (*http.Response, error) {
                res := httpmock.NewStringResponse(200, "ok")
                return res, nil
            },
        )

        content, err := GetContentUseMock()
        if err != nil {
            t.Errorf("error should be nil: %v", err)
        }

        if content != "ok" {
            t.Errorf("content invalid: %v", content)
        }
    })
}
  • RegisterResponderに何を返すのかを書くだけ。