7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-08-03

Golangでテストを書く際にgomockを使用したのですが、使い方がよくわからなかったのでまとめてみます。

1. gomockとは

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

2. インストール

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

3. テスト対象のコード

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

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()を呼んでいますが、中身を見てみましょう。

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でファイルに出力します

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

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.goGET()と同じinterfaceのmockが生成されています。
テスト実行時にGET()をmockの中身に差し替えます。

5. テストコードを書く

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


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なので、この戻り値に合うように設定しています。


	test := NewGetAccess(testMock)
	statuscode, err := test.GetAccessFunc()

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

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

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

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?