LoginSignup
30

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-03-04

はじめに

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

gomockとは

テスト用に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

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
30