gomockを使用してテストをしてみる

`Golang`でテストを書く際に[gomock](https://github.com/golang/mock)を使用したのですが、使い方がよくわからなかったのでまとめてみます。

# 1. gomockとは

テスト時に作るモックを自動生成してくれるツール。
1からモックを作るのも良いですが、自動生成してもらえるだけで効率的にテストを進められます。

# 2. インストール

```
$ go get github.com/golang/mock/gomock
$ go get github.com/golang/mock/mockgen
```

# 3. テスト対象のコード

以下のコードの`GetAccessFunc()`をテストしてみたいと思います。

```golang:getaccess/getAccess.go
package getaccess

import "gomock-sample/httpaccess"

type message struct {
	message string
}

type GetAccess interface {
	GetAccessFunc() (statuscode int, err error)
}

type getaccess struct {
	Ta httpaccess.TestAPI
}

func NewGetAccess(ta httpaccess.TestAPI) GetAccess {
	return &getaccess{ta}
}

func (ga getaccess) GetAccessFunc() (statuscode int, err error) {
	var responceJSON message
	statuscode, err = ga.Ta.GET("/auth/test", "authorizationkey", &responceJSON)
	if err != nil {
		return statuscode, err
	}
	return statuscode, nil
}

```

途中で`GET()`を呼んでいますが、中身を見てみましょう。

```golang:httpaccess/httpAccess.go
package httpaccess

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
)

type TestAPI interface {
	GET(urlString string, authorization string, responseJSON interface{}) (statuscode int, err error)
}

type testapi struct {
	BaseURL string
}

func NewTestAPI(baseURL string) TestAPI {
	return &testapi{baseURL}
}

// GET return error
func (api testapi) GET(urlString string, authorization string, responseJSON interface{}) (statuscode int, err error) {
	url := api.BaseURL + urlString
	request, err := http.NewRequest("GET", url, nil)
	request.Header.Add("Authorization", authorization)
	request.Header.Add("Content-Type", "application/json")

	client := new(http.Client)
	response, err := client.Do(request)
	if err != nil {
		return http.StatusBadRequest, err
	}
	defer response.Body.Close()

	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return response.StatusCode, err
	}
	json.Unmarshal(body, &responseJSON)
	return response.StatusCode, nil
}
```
`GET()`は外部のAPIを実行するための処理であるため、テストを書く時点では一度呼びたくない。
`httpAccess.go`をmock化してテストを書いていきます。

# 4. mockの生成

以下をコマンドラインで実行します。

```
$ mockgen -source httpaccess/httpAccess.go -destination mock_httpaccess/httpAccess_mock.go
```
`-source`でコードを出力、`-destination`でファイルに出力します

生成されたファイルを見てみます。

```golang:mock_httpaccess/httpAccess_mock.go
// Code generated by MockGen. DO NOT EDIT.
// Source: httpaccess/httpAccess.go

// Package mock_httpaccess is a generated GoMock package.
package mock_httpaccess

import (
	gomock "github.com/golang/mock/gomock"
	reflect "reflect"
)

// MockTestAPI is a mock of TestAPI interface
type MockTestAPI struct {
	ctrl     *gomock.Controller
	recorder *MockTestAPIMockRecorder
}

// MockTestAPIMockRecorder is the mock recorder for MockTestAPI
type MockTestAPIMockRecorder struct {
	mock *MockTestAPI
}

// NewMockTestAPI creates a new mock instance
func NewMockTestAPI(ctrl *gomock.Controller) *MockTestAPI {
	mock := &MockTestAPI{ctrl: ctrl}
	mock.recorder = &MockTestAPIMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockTestAPI) EXPECT() *MockTestAPIMockRecorder {
	return m.recorder
}

// GET mocks base method
func (m *MockTestAPI) GET(urlString, authorization string, responseJSON interface{}) (int, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "GET", urlString, authorization, responseJSON)
	ret0, _ := ret[0].(int)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// GET indicates an expected call of GET
func (mr *MockTestAPIMockRecorder) GET(urlString, authorization, responseJSON interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GET", reflect.TypeOf((*MockTestAPI)(nil).GET), urlString, authorization, responseJSON)
}

```
`httpAccess.go`の`GET()`と同じinterfaceのmockが生成されています。
テスト実行時に`GET()`をmockの中身に差し替えます。

# 5. テストコードを書く

ステータスコードが200,エラーがnilで帰ってくることを確認するコードを書いてみます。

```golang

ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	testMock := mock.NewMockTestAPI(ctrl)
	var responceJSON message
	testMock.EXPECT().GET("/auth/test", "authorizationkey", &responceJSON).Return(http.StatusOK, nil)
```
生成したmockの設定を行っていきます。
`EXPECT()`を使用することで、モックのメソッドが呼び出されたのかテストできます。
`GET()`の引数で期待される引数を設定。
`Return`の引数でmockの戻り値を設定。
元々の`GET()`の戻り値はstatuscodeとerrなので、この戻り値に合うように設定しています。

```golang

	test := NewGetAccess(testMock)
	statuscode, err := test.GetAccessFunc()
```
getAccess()で必要とされているinterfaceにteskmockを入れていきます。
`test.GetAccessFunc()`と呼び出すことで、mockのGETを使用してテストを行えます。

最終的なコードは以下の通り。

```golang:getaccess/getAccess_test.go
package getaccess

import (
	mock "gomock-sample/mock_httpaccess"
	"net/http"
	"testing"

	"github.com/golang/mock/gomock"
)

func TestGetAccessFunc(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	testMock := mock.NewMockTestAPI(ctrl)
	var responceJSON message
	testMock.EXPECT().GET("/auth/test", "authorizationkey", &responceJSON).Return(http.StatusOK, nil)

	test := NewGetAccess(testMock)
	statuscode, err := test.GetAccessFunc()
	if err != nil {
		t.Error("failed Test")
	}
	if statuscode != http.StatusOK {
		t.Error("failed Test")
	}
}
```
今回はテストしても微妙なコードで解説しましたが、mockで戻り値を固定化することにより、実際の実行結果に左右されず、エラーハンドリングをテストすることが容易になります。

今回のコードはgithubに実行できる状態で置いてありますので、実行してみてください。
https://github.com/yuina1056/gomock-sample

# 参考
[Go Mockでインタフェースのモックを作ってテストする #golang](https://qiita.com/tenntenn/items/24fc34ec0c31f6474e6d)