gockでHTTPリクエストをMockする

はじめに

最近はGo言語でWebアプリを書く際に使う諸々のライブラリを試してみているのですが、今はMockサーバをいろいろ見ていました。
Goの場合は標準ライブラリでもhttptestでいろいろ用意されていているみたいです。ただちょっと、そのまま使うには手間が多そうに感じ、ほかを探してるとgockというのが良さげだったのでちょっと試してみようと思います。

gockとは

Go製のHTTPのMockライブラリーです。
以下のような特徴があります。

  • 宣言的なMockの定義
  • ビルトインのJSON/XMLのヘルパー
  • Mockとリアルワールドの切り替え機能
  • gentlemanなどのクライアントとの連携機能

gockはhttp.Clientで利用されるhttp.DefaultTransportかもしくはカスタムhttp.Transportを経由して、HTTPのアウトバウンドのリクエストをモックします。 登録されたMockがFIFOでリクエストにマッチするかの検証が行われ、マッチした場合MockのHTTPレスポンスを返します。
そして、どのMockにもマッチしなかった場合は基本的にはエラーが起こるようです。ただし、リアルネットワークモードが有効化されている場合は実際のリクエストが代わりに実行されます。

このブログではあまり複雑なことはせずにひとまず動かしてみるところまでやってみようかと思います。

使ってみる

環境

プログラムを動かす環境は以下の通り

$ go version
go version go1.16 linux/amd64

$ uname -srvmpio
Linux 5.4.0-65-generic #73-Ubuntu SMP Mon Jan 18 17:25:17 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
LSB Version:    core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.2 LTS
Release:    20.04
Codename:   focal

プロジェクトを作る

プロジェクトを作成して、TestifyとGockをインストトールします。

$ go get github.com/stretchr/testify
go get: added github.com/stretchr/testify v1.7.0

$ go get -u gopkg.in/h2non/gock.v1
go get: added gopkg.in/h2non/gock.v1 v1.0.16

これで下準備までは環境です。

Pongの文字列を返すMockを作る

準備ができたので、早速使っていこうと思います。
localhost:8081/pingに対して”pong”の文字列のレスポンス返す場合は以下のようにします。

import (
    "github.com/stretchr/testify/assert"
    "gopkg.in/h2non/gock.v1"
    "io"
    "log"
    "net/http"
    "testing"
)

const (
    MOCK_URL  = "localhost:8082"
    PING_PATH = "/ping"
)

func TestName(t *testing.T) {
    defer gock.Off()

    gock.New(MOCK_URL).
        Get(PING_PATH).
        Reply(200).
        BodyString("pong")

    res, err := http.Get("http://" + MOCK_URL + PING_PATH)
    handleError(err)

    bodyByte, err := io.ReadAll(res.Body)
    handleError(err)

    assert.Equal(t, 200, res.StatusCode)
    assert.Equal(t, "pong", string(bodyByte))

    assert.Equal(t, gock.IsDone(), true)
}

func handleError(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

gockでMockをたてる場合はgock.New(MOCK_URL)のように宣言的にMockを定義することができます。また、リクエストのヘッダーやボディ、リクエストパラムでマッピングを作成したい場合はReply(200).より前のリクエストのパターンを構成するビルダーのメソットチェインの中で、それぞれMatchHeaderMatchBodyJSONJsonのリクエストボディを受け取る場合)、MatchHeaderなどの関数を呼び出すことで行えます。
例えば、MatchHeader("x-api-version", "1.[0-9]")のように記述することができ、この記述の場合、ヘッダーにキーがx-api-versionで値が1.(0から9までの数字)を含むリクエストに対してマッチングします。

レスポンスを作成する場合はReply()関数を呼び出し、レスポンスボディは今回は文字列を返すのでBodyStringを呼び出しています。JSONの値を返したい場合はJSONメソッドを呼び出して、JSON(map[string]string{"foo": "bar"})のように記述します。

アサーション部分では、最後のところがポイントでgock.IsDone()を呼び出すことですべての設定したモックが呼び出されているかの検証を行なうことができます。