Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Goでメソッドを簡単にモック化する【gomock】

More than 1 year has passed since last update.

はじめに

Goでモックを使ったテストを書く際によく利用されるgomockを触ってみました。

gomockとは

https://github.com/golang/mock

テスト用にGoのインターフェースとメソッドをモック化するライブラリです。

インストール

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

使ってみる

モックにされる対象のインターフェース

今回は、Barというメソッドを持つFooインターフェースを例にします。

foo.go
type Foo interface {
    Bar(x int) int
}

モックを自動生成する

mockgenコマンドでモックファイルを自動生成します。
-sourceオプションでinterfaceが宣言されているモック対象のファイルを指定し、-destinationオプションでモックファイルの出力先を指定します。
mockgenコマンドには他にも色々オプションがあるみたいです。詳しくはこちら
mockファイルは mock_<package_name>/ ディレクトリ配下に mock_<package_name>.go という名前で作るのが通例ぽいです。

それでは作っていきましょう。

$ mkdir mock_foo
$ mockgen -source foo.go -destination mock_foo/mock_foo.go

生成されたファイルが以下です。
メソッドを実装し、インターフェースを満たす構造体が定義されています。構造体名はディレクトリ名やファイル名に連動しています。

mock_foo/mock_foo.go
// Code generated by MockGen. DO NOT EDIT.
// Source: foo.go

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

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

// MockFoo is a mock of Foo interface
type MockFoo struct {
    ctrl     *gomock.Controller
    recorder *MockFooMockRecorder
}

// MockFooMockRecorder is the mock recorder for MockFoo
type MockFooMockRecorder struct {
    mock *MockFoo
}

// NewMockFoo creates a new mock instance
func NewMockFoo(ctrl *gomock.Controller) *MockFoo {
    mock := &MockFoo{ctrl: ctrl}
    mock.recorder = &MockFooMockRecorder{mock}
    return mock
}

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

// Bar mocks base method
func (m *MockFoo) Bar(x int) int {
    m.ctrl.T.Helper()
    ret := m.ctrl.Call(m, "Bar", x)
    ret0, _ := ret[0].(int)
    return ret0
}

// Bar indicates an expected call of Bar
func (mr *MockFooMockRecorder) Bar(x interface{}) *gomock.Call {
    mr.mock.ctrl.T.Helper()
    return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bar", reflect.TypeOf((*MockFoo)(nil).Bar), x)
}

テストファイル

モックを呼び出す簡単な例を示します。
実際にはモックを呼び出すだけではなく、モックで外部的要因を固定化して、本当にテストしたい部分をテストする使い方になるはずですが、今回はgomockを理解するために、呼ぶ出すだけにしています。

foo_test.go
func TestFoo(t *testing.T) {
    // コントローラーの生成
    ctrl := gomock.NewController(t)

    defer ctrl.Finish()

    // モックの生成
    m := mock_foo.NewMockFoo(ctrl)

    // モックの対象メソッドと引数と返り値を設定する
    m.
        EXPECT().
        Bar(99).
        Return(101)

    // モックの呼び出し
    m.Bar(99)
}

上記テストファイルを実行すると以下の実行結果になります。

成功結果
ok      study/mock  (cached)
Success: Tests passed.

例えば、 m.Bar(100) でモックに設定していない引数で実行すると以下のように失敗します。

失敗結果
--- FAIL: TestFoo (0.00s)
    /Users/XXX/go/src/study/mock/foo_test.go:25: Unexpected call to *mock_foo.MockFoo.Bar([100]) at /Users/XXX/go/src/study/mock/mock_foo/mock_foo.go:38 because: 
        Expected call at /Users/XXX/go/src/study/mock/foo_test.go:22 doesn't match the argument at index 0.
        Got: 100
        Want: is equal to 99
    asm_amd64.s:522: missing call(s) to *mock_foo.MockFoo.Bar(is equal to 99) /Users/XXX/go/src/study/mock/foo_test.go:22
    asm_amd64.s:522: aborting test due to missing call(s)
FAIL
FAIL    study/mock  0.007s
Error: Tests failed.

参考

Go Mockでインタフェースのモックを作ってテストする #golang

gold-kou
NTT米屋➡︎ZOZOテクノロジーズ スクラムマスター兼バックエンドエンジニアです。 GoとかgRPCとかOpenAPIとか。 外部記憶として学んだことを記事として残しています。 私の記事は個人的なものであり、会社を代表するものではございません。
zozotech
70億人のファッションを技術の力で変えていく
https://tech.zozo.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away