gomockを利用した背景を軽く話す。cognitoのユーザープールからユーザー情報を引っ張ってきて、その情報を元にある情報を取得するというAPIを開発していた。このAPIを生成するにあたって、AWS cognitoからユーザー情報を取得するという部分の処理をスタブ化する必要が発生した。というのも原則として外部状態に依存するテストは良くないからだ。理由は次の通り。
・ネットワークの問題が発生したときテストに失敗する
・外部サービスが従量課金制度の場合、テストごとに料金が発生する
これによりGoでスタブを生成するパッケージを探していた所このgomockと呼ばれるパッケージがよく使われているようなので使うことにした。
gomockとは
https://github.com/golang/mock
例えばhoge.go
というファイルがあったときにgo mockを使えばmock_go.go
といった感じにhoge.go
のモックを一瞬で生成してくれる。
gomockをインストールする
$ go get github.com/golang/mock/gomock
$ go install github.com/golang/mock/mockgen
gomockでモックを生成するhoge.go
という以下のファイルがあるとして
package hoge
type Hoge interface {
Foo(s string) int
}
このコードをモック化する。モック化するにあたって、mockgen
というコマンドを叩く。
$ mockgen -source hoge.go -destination mock_hoge.go
destination
コマンドで生成されるモックファイルの名前とディレクトリを指定することができる。そして生成されたmock_hoge.go
はこんな感じ。
// Code generated by MockGen. DO NOT EDIT.
// Source: hoge.go
// Package mock_hoge is a generated GoMock package.
package mock_hoge
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockHoge is a mock of Hoge interface
type MockHoge struct {
ctrl *gomock.Controller
recorder *MockHogeMockRecorder
}
// MockHogeMockRecorder is the mock recorder for MockHoge
type MockHogeMockRecorder struct {
mock *MockHoge
}
// NewMockHoge creates a new mock instance
func NewMockHoge(ctrl *gomock.Controller) *MockHoge {
mock := &MockHoge{ctrl: ctrl}
mock.recorder = &MockHogeMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockHoge) EXPECT() *MockHogeMockRecorder {
return m.recorder
}
// Foo mocks base method
func (m *MockHoge) Foo(s string) int {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Foo", s)
ret0, _ := ret[0].(int)
return ret0
}
// Foo indicates an expected call of Foo
func (mr *MockHogeMockRecorder) Foo(s interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Foo", reflect.TypeOf((*MockHoge)(nil).Foo), s)
}
ファイル更新時に自動でモックを生成する
これは少し踏み入った話だが、モック対象ファイル、今回で言うhoge.go
が更新された時、いちいちmockgen
コマンドを叩くのは正直めんどくさい。これを回避する方法がある。インターフェースが定義されているモック対象ファイルに限って、先頭の行に以下のコメントを付与すれば良い。
//go:generate mockgen -source=$GOFILE -destination=mock_$GOFILE -package=$GOPACKAGE
こうすることでインターフェースを修正して、go generate
が実行すれば最新のモックが生成される。
gomockでスタブを生成するmock_hoge.go
を使ってスタブを生成してみる。(実装の都合上mock_hoge.go
のpackageをhoge
に変更する)
スタブを使ったテストコードを書くためにhoge_test.go
というファイルを用意した。
package hoge
import (
"testing"
"github.com/golang/mock/gomock"
)
func TestHoge1(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockHoge(ctrl)
m.EXPECT().Foo("hoge").Return(1)
t.Log("result:", m.Foo("hoge"))
}
mock instance
を生成して、EXPECT
でスタブを生成することができ、Return
でその関数の返り値を定義することができる。このテストコードを実行してみる。出力に1が出てきたらスタブができていることが証明できる。
$ go test -v -timeout 30s tmp/hoge
=== RUN TestHoge1
TestHoge1: hoge_test.go:16: result: 1
--- PASS: TestHoge1 (0.00s)
PASS
ok tmp/hoge 0.004s
result: 1
が出力されているので無事にスタブを生成することができた。大枠はだいたいこんな感じ。細かい設定などはREADMEを参考に。