コードをテストするときにmockを使うことが多いと思いますが、goには依存注入したinterfaceのmockを自動生成してくれるライブラリがあります。
その使い方をまとめてみました。
サンプルコード
現在時刻を返してくれるコードです。clockappディレクトリというパッケージ名です。
$ tree
.
├── controller
│ └── controller.go
├── go.mod
├── main.go
└── service
└── clock.go
module clockapp
go 1.24.4
// main.go
package main
import (
"clockapp/controller"
"clockapp/service"
"fmt"
"time"
)
func main() {
loc, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
panic(err)
}
svc := &service.Clock{Location: loc}
ctrl := controller.UserController{Clock: svc}
fmt.Println(ctrl.ResponseCurrentTime())
}
// Package controller provides handlers for clock-related operations.
package controller
import "clockapp/service"
type UserController struct {
Clock service.ClockInterface
}
func (c *UserController) ResponseCurrentTime() string {
return "It is " + c.Clock.GetCurrentTime() + " now."
}
// Package service provides clock-related functionality.
package service
import "time"
type ClockInterface interface {
GetCurrentTime() string
}
type Clock struct {
Location *time.Location
}
func (r *Clock) GetCurrentTime() string {
now := time.Now().In(r.Location)
return now.Format("2006-01-02 15:04:05 MST")
}
これを実行すると例えば
$ go run main.go
It is 2025-10-14 22:48:22 JST now.
のようになります。
テストコード
mockの自動生成
service.Clock.GetCurrentTime
はいつも同じ値を返してくれるわけではないので、controller.UserController.ResponseCurrentTime
のテストには不向きです。そこで、読み込むcontroller.UserController.Clock
をmockに置き換えます。
mockgenのインストール
$ go install github.com/golang/mock/mockgen@latest
$ export PATH=$PATH:$(go env GOPATH)/bin
mockパッケージの自動生成
$ mockgen -source=service/clock.go -destination=mocks/clock_mock.go -package=mocks
を実行すればいいのですが、以下のようにコードにコメントを書くとgo generate ./service
で各ファイルのmockgen
を自動で実行してくれます。
//go:generate mockgen -source=clock.go -destination=../mocks/clock_mock.go -package=mocks
// Package service provides clock-related functionality.
package service
import "time"
type ClockInterface interface {
GetCurrentTime() string
}
type Clock struct {
Location *time.Location
}
func (r *Clock) GetCurrentTime() string {
now := time.Now().In(r.Location)
return now.Format("2006-01-02 15:04:05 MST")
}
mockのパッケージがimportされるので
go mod tidy
で依存関係を追加しておきます。
ちなみにコードを修正した後にgo generate
を実行すれば、mockは修正内容に合わせて上書きされます。
テストコード
clockappパッケージにはservice
、controller
に1個ずつ、構造体があるので、これらをそれぞれテストします。controller
のテストで先ほど作ったmockを使います。
package controller
import (
"clockapp/mocks"
"testing"
"github.com/golang/mock/gomock"
)
func TestUserController_ResponseCurrentTime(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockClock := mocks.NewMockClockInterface(ctrl)
mockClock.EXPECT().GetCurrentTime().Return("2025-10-14 23:59:59 JST")
uc := UserController{Clock: mockClock}
got := uc.ResponseCurrentTime()
want := "It is 2025-10-14 23:59:59 JST now."
if got != want {
t.Errorf("expected %q, got %q", want, got)
}
}
package service
import (
"testing"
"time"
)
func TestClock_GetCurrentTime(t *testing.T) {
loc := time.FixedZone("JST", 9*60*60)
clock := &Clock{Location: loc}
got := clock.GetCurrentTime()
if len(got) != len("2006-01-02 15:04:05 MST") {
t.Errorf("unexpected format: got %s", got)
}
if got[len(got)-3:] != "JST" {
t.Errorf("expected JST timezone, got %s", got)
}
}
テストコードを追加するとこんな感じの構成になります。
$ tree
.
├── controller
│ ├── controller.go
│ └── controller_test.go
├── go.mod
├── go.sum
├── main.go
├── mocks
│ └── clock_mock.go
└── service
├── clock.go
└── clock_test.go
実行するとこんな感じです。
$ go test -v ./controller ./service
=== RUN TestUserController_ResponseCurrentTime
--- PASS: TestUserController_ResponseCurrentTime (0.00s)
PASS
ok clockapp/controller (cached)
=== RUN TestClock_GetCurrentTime
--- PASS: TestClock_GetCurrentTime (0.00s)
PASS
ok clockapp/service (cached)
大きなコードになるといちいちmockを手書きで作ったり修正したりするのは面倒なので、こういう機能は便利ですね。