概要
GO言語のテストコードモジュール GoMockを紹介してます。
GoMockはテストを実行するときにDBや外部APIを疎通しなくてもプログラミングが正しく動いているかどうかテストコードで確認することができます。
GoMockの導入手順
GoMockを使ったコードはGithub上で公開しています。HomeAPIというプロジェクトの中でGoMockテストコードを記載しています。以下のGitHubボタンをクリックするとGitHubページに遷移します

GoMock インストール
go mockを利用するにはモジュールをインストールする必要があります。
以下のコマンドを入力してください。
go get -u github.com/golang/mock/gomock
go install github.com/golang/mock/mockgen
Go 1.16以上 go instalコマンドが1.16以上のため
GoMock コードを導入
go generateをコード内に記入
Go言語ではコードを自動生成するコマンド go generate が存在します。
クリーンアーキテクチャの設計であればrepository インターフェース層の以下にgo:generateを追記します。
//go:generate mockgen -source=./temperature_repository.go -package=repositorymock -destination=./mock/temperature_repository.go
全体的なコード
"homeapi/interfaces/repository"
//go:generate mockgen -source=./temperature_repository.go -package=repositorymock -destination=./mock/temperature_repository.go
func NewTemperatureRepository(db *gorm.DB) repository.TemperatureRepository {
return repository.TemperatureRepository{
// TemperatureRepository Temperature Repository
type TemperatureRepository interface {
List() ([]domain.Temperature, error)
Insert(*domain.Temperature) error
package repository
import (
"homeapi/domain"
"homeapi/interfaces/repository"
"github.com/jinzhu/gorm"
)
//go:generate mockgen -source=./temperature_repository.go -package=repositorymock -destination=./mock/temperature_repository.go
func NewTemperatureRepository(db *gorm.DB) repository.TemperatureRepository {
return repository.TemperatureRepository{
Database: db,
}
}
// TemperatureRepository Temperature Repository
type TemperatureRepository interface {
List() ([]domain.Temperature, error)
Insert(*domain.Temperature) error
}
package repository
import (
"homeapi/domain"
"homeapi/interfaces/repository"
"github.com/jinzhu/gorm"
)
//go:generate mockgen -source=./temperature_repository.go -package=repositorymock -destination=./mock/temperature_repository.go
func NewTemperatureRepository(db *gorm.DB) repository.TemperatureRepository {
return repository.TemperatureRepository{
Database: db,
}
}
// TemperatureRepository Temperature Repository
type TemperatureRepository interface {
List() ([]domain.Temperature, error)
Insert(*domain.Temperature) error
}
プロジェクト直下でコマンド
go generateのコメントを入れたら以下のコマンドを実行してください。
go generate ./...
自動生成されたコード
インターフェースに合わせたリクエストとレスポンスの値に対してモックが作成されます。
// Code generated by MockGen. DO NOT EDIT.
// Source: ./temperature_repository.go
// Package repositorymock is a generated GoMock package.
gomock "github.com/golang/mock/gomock"
// MockTemperatureRepository is a mock of TemperatureRepository interface.
type MockTemperatureRepository struct {
recorder *MockTemperatureRepositoryMockRecorder
// MockTemperatureRepositoryMockRecorder is the mock recorder for MockTemperatureRepository.
type MockTemperatureRepositoryMockRecorder struct {
mock *MockTemperatureRepository
// NewMockTemperatureRepository creates a new mock instance.
func NewMockTemperatureRepository(ctrl *gomock.Controller) *MockTemperatureRepository {
mock := &MockTemperatureRepository{ctrl: ctrl}
mock.recorder = &MockTemperatureRepositoryMockRecorder{mock}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTemperatureRepository) EXPECT() *MockTemperatureRepositoryMockRecorder {
// Insert mocks base method.
func (m *MockTemperatureRepository) Insert(arg0 *domain.Temperature) error {
ret := m.ctrl.Call(m, "Insert", arg0)
ret0, _ := ret[0].(error)
// Insert indicates an expected call of Insert.
func (mr *MockTemperatureRepositoryMockRecorder) Insert(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockTemperatureRepository)(nil).Insert), arg0)
// List mocks base method.
func (m *MockTemperatureRepository) List() ([]domain.Temperature, error) {
ret := m.ctrl.Call(m, "List")
ret0, _ := ret[0].([]domain.Temperature)
ret1, _ := ret[1].(error)
// List indicates an expected call of List.
func (mr *MockTemperatureRepositoryMockRecorder) List() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockTemperatureRepository)(nil).List))
// Code generated by MockGen. DO NOT EDIT.
// Source: ./temperature_repository.go
// Package repositorymock is a generated GoMock package.
package repositorymock
import (
domain "homeapi/domain"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockTemperatureRepository is a mock of TemperatureRepository interface.
type MockTemperatureRepository struct {
ctrl *gomock.Controller
recorder *MockTemperatureRepositoryMockRecorder
}
// MockTemperatureRepositoryMockRecorder is the mock recorder for MockTemperatureRepository.
type MockTemperatureRepositoryMockRecorder struct {
mock *MockTemperatureRepository
}
// NewMockTemperatureRepository creates a new mock instance.
func NewMockTemperatureRepository(ctrl *gomock.Controller) *MockTemperatureRepository {
mock := &MockTemperatureRepository{ctrl: ctrl}
mock.recorder = &MockTemperatureRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTemperatureRepository) EXPECT() *MockTemperatureRepositoryMockRecorder {
return m.recorder
}
// Insert mocks base method.
func (m *MockTemperatureRepository) Insert(arg0 *domain.Temperature) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Insert indicates an expected call of Insert.
func (mr *MockTemperatureRepositoryMockRecorder) Insert(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockTemperatureRepository)(nil).Insert), arg0)
}
// List mocks base method.
func (m *MockTemperatureRepository) List() ([]domain.Temperature, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List")
ret0, _ := ret[0].([]domain.Temperature)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockTemperatureRepositoryMockRecorder) List() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockTemperatureRepository)(nil).List))
}
// Code generated by MockGen. DO NOT EDIT.
// Source: ./temperature_repository.go
// Package repositorymock is a generated GoMock package.
package repositorymock
import (
domain "homeapi/domain"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockTemperatureRepository is a mock of TemperatureRepository interface.
type MockTemperatureRepository struct {
ctrl *gomock.Controller
recorder *MockTemperatureRepositoryMockRecorder
}
// MockTemperatureRepositoryMockRecorder is the mock recorder for MockTemperatureRepository.
type MockTemperatureRepositoryMockRecorder struct {
mock *MockTemperatureRepository
}
// NewMockTemperatureRepository creates a new mock instance.
func NewMockTemperatureRepository(ctrl *gomock.Controller) *MockTemperatureRepository {
mock := &MockTemperatureRepository{ctrl: ctrl}
mock.recorder = &MockTemperatureRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockTemperatureRepository) EXPECT() *MockTemperatureRepositoryMockRecorder {
return m.recorder
}
// Insert mocks base method.
func (m *MockTemperatureRepository) Insert(arg0 *domain.Temperature) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Insert indicates an expected call of Insert.
func (mr *MockTemperatureRepositoryMockRecorder) Insert(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockTemperatureRepository)(nil).Insert), arg0)
}
// List mocks base method.
func (m *MockTemperatureRepository) List() ([]domain.Temperature, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List")
ret0, _ := ret[0].([]domain.Temperature)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockTemperatureRepositoryMockRecorder) List() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockTemperatureRepository)(nil).List))
}
このコードを修正するのはNGです。同じコマンドを打つとまた元に戻ってしまいます。
テストコード実装
テストコードを書いている場所はこちらをクリック
取得系のコード(SELECT)
type serverMocks struct {
temperatureRepository *repositorymock.MockTemperatureRepository
func newMocks(ctrl *gomock.Controller) (*TemperatureUsecase, *serverMocks) {
temperatureRepository: repositorymock.NewMockTemperatureRepository(ctrl),
temperatureUsecase := &TemperatureUsecase{
TemperatureRepository: mocks.temperatureRepository,
return temperatureUsecase, mocks
func TestList(t *testing.T) {
t.Run("success", func(t *testing.T) {
ctrl := gomock.NewController(t)
temperatureUsecase, mocks := newMocks(ctrl)
temperature := []domain.Temperature{
mocks.temperatureRepository.EXPECT().List().Return(temperature, nil)
got, err := temperatureUsecase.List()
t.Errorf("error message : %v", err)
want := []ports.TemperatureOutputPort{
assert.Equal(t, &want, got)
type serverMocks struct {
temperatureRepository *repositorymock.MockTemperatureRepository
}
func newMocks(ctrl *gomock.Controller) (*TemperatureUsecase, *serverMocks) {
mocks := &serverMocks{
temperatureRepository: repositorymock.NewMockTemperatureRepository(ctrl),
}
temperatureUsecase := &TemperatureUsecase{
TemperatureRepository: mocks.temperatureRepository,
}
return temperatureUsecase, mocks
}
func TestList(t *testing.T) {
t.Run("success", func(t *testing.T) {
nowTime := time.Now()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
temperatureUsecase, mocks := newMocks(ctrl)
temperature := []domain.Temperature{
{
ID: 12,
Temp: "22",
Humi: "61",
CreatedAt: nowTime,
},
{
ID: 13,
Temp: "25",
Humi: "63",
CreatedAt: nowTime,
},
}
mocks.temperatureRepository.EXPECT().List().Return(temperature, nil)
got, err := temperatureUsecase.List()
require.NoError(t, err)
if err != nil {
t.Errorf("error message : %v", err)
}
want := []ports.TemperatureOutputPort{
{
ID: 12,
Temp: "22",
Humi: "61",
},
{
ID: 13,
Temp: "25",
Humi: "63",
},
}
assert.Equal(t, &want, got)
})
}
type serverMocks struct {
temperatureRepository *repositorymock.MockTemperatureRepository
}
func newMocks(ctrl *gomock.Controller) (*TemperatureUsecase, *serverMocks) {
mocks := &serverMocks{
temperatureRepository: repositorymock.NewMockTemperatureRepository(ctrl),
}
temperatureUsecase := &TemperatureUsecase{
TemperatureRepository: mocks.temperatureRepository,
}
return temperatureUsecase, mocks
}
func TestList(t *testing.T) {
t.Run("success", func(t *testing.T) {
nowTime := time.Now()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
temperatureUsecase, mocks := newMocks(ctrl)
temperature := []domain.Temperature{
{
ID: 12,
Temp: "22",
Humi: "61",
CreatedAt: nowTime,
},
{
ID: 13,
Temp: "25",
Humi: "63",
CreatedAt: nowTime,
},
}
mocks.temperatureRepository.EXPECT().List().Return(temperature, nil)
got, err := temperatureUsecase.List()
require.NoError(t, err)
if err != nil {
t.Errorf("error message : %v", err)
}
want := []ports.TemperatureOutputPort{
{
ID: 12,
Temp: "22",
Humi: "61",
},
{
ID: 13,
Temp: "25",
Humi: "63",
},
}
assert.Equal(t, &want, got)
})
}
挿入系のコード(INSERT)
func TestInsert(t *testing.T) {
t.Run("success", func(t *testing.T) {
nowTime, err := util.JapaneseNowTime()
ctrl := gomock.NewController(t)
temperatureUsecase, mocks := newMocks(ctrl)
temperature := &domain.Temperature{
dofunc := func(temperature *domain.Temperature) *domain.Temperature {
mocks.temperatureRepository.EXPECT().Insert(temperature).Do(dofunc).Return(nil)
request := &ports.TemperatureInputPort{
got, err := temperatureUsecase.Create(request)
want := ports.TemperatureOutputPort{
assert.Equal(t, want, got)
func TestInsert(t *testing.T) {
t.Run("success", func(t *testing.T) {
nowTime, err := util.JapaneseNowTime()
if err != nil {
t.Error(err)
}
// nowTime := time.Now()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
temperatureUsecase, mocks := newMocks(ctrl)
temperature := &domain.Temperature{
Temp: "20",
Humi: "55",
CreatedAt: nowTime,
}
dofunc := func(temperature *domain.Temperature) *domain.Temperature {
temperature.ID = 71
return temperature
}
mocks.temperatureRepository.EXPECT().Insert(temperature).Do(dofunc).Return(nil)
request := &ports.TemperatureInputPort{
Temp: "20",
Humi: "55",
}
got, err := temperatureUsecase.Create(request)
require.NoError(t, err)
want := ports.TemperatureOutputPort{
ID: 71,
Temp: "20",
Humi: "55",
}
assert.Equal(t, want, got)
})
}
func TestInsert(t *testing.T) {
t.Run("success", func(t *testing.T) {
nowTime, err := util.JapaneseNowTime()
if err != nil {
t.Error(err)
}
// nowTime := time.Now()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
temperatureUsecase, mocks := newMocks(ctrl)
temperature := &domain.Temperature{
Temp: "20",
Humi: "55",
CreatedAt: nowTime,
}
dofunc := func(temperature *domain.Temperature) *domain.Temperature {
temperature.ID = 71
return temperature
}
mocks.temperatureRepository.EXPECT().Insert(temperature).Do(dofunc).Return(nil)
request := &ports.TemperatureInputPort{
Temp: "20",
Humi: "55",
}
got, err := temperatureUsecase.Create(request)
require.NoError(t, err)
want := ports.TemperatureOutputPort{
ID: 71,
Temp: "20",
Humi: "55",
}
assert.Equal(t, want, got)
})
}
ハマりポイント
Listに対するコード
mocks.temperatureRepository.EXPECT().List().Return(temperature, nil)
Insertに対するコード
mocks.temperatureRepository.EXPECT().Insert(temperature).Do(dofunc).Return(nil)
EXPECT()の部分はなかなかテストが通りずらいことを経験された方が多いです。具体的になにを入れれがいいのか解説していきます。データは正しく挿入しないと動かないということとエラーメッセージがわかりずらいところです。
インターフェースが以下のコードに対するテストコードです。
Listに対するインターフェース
List() ([]domain.Temperature, error)
Insertに対するインターフェース
Insert(*domain.Temperature) error
基本的に同じ型のデータを入れてあげればいいのですがInsertにあるdofuncはID等のデータベースで自動で割り振られるものを定義しておくことで予想するテスト結果を返すことができます。
掌田津耶乃 秀和システム 2021年03月06日頃
まとめ
テストではDBぐらいはテストDB立ち上げてテストした方がより明確にテストできるのでいいですが、外部APIを利用する時はまさにMockを使った方が安心して利用することができます。