Goでモックを使うテストを書く時に利用するgomockについて簡単に整理しておく

使い方

大まかに3ステップ

  1. Install
  2. Generate
  3. Test

Install

テストの際に使うパッケージとGenerateコマンドのためのパッケージをそれぞれインストール

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

Generate

source modereflect mode の2種類の使い方がある

source mode

ソースファイルを指定

mockgen -source={filepath} [options]

-destination を指定しないと標準出力に出力される

reflect mode

Import pathと対象のシンボルを指定

mockgen {import path} {symbol[,...]}
  • 複数のシンボルを指定する際はカンマ区切り指定
  • reflect mode は reflection によって interface を理解するプログラムをビルドして mock を生成するらしいが、イマイチ違いは理解できてない(単純なケースなら source mode で十分とある)

Test

READMEには Mocks と Stubs の2通りの使い方がある
とはいえ、使い方はほぼ同じなのでここではMockの方だけメモを残しておく

テストの中で登場する Controller は、 Mock で起きたことを監視する役割を持つと思っておけば良い

type Foo interface {
  Bar(x int) int
}

func SUT(f Foo) {
 // ...
}

func TestFoo(t *testing.T) {
  ctrl := gomock.NewController(t)

  // Assert that Bar() is invoked.
  defer ctrl.Finish()

  m := NewMockFoo(ctrl)

  // Asserts that the first and only call to Bar() is passed 99.
  // Anything else will fail.
  m.
    EXPECT().
    Bar(gomock.Eq(99)).
    Return(101)

  SUT(m)
}

上からざっくり解説

  1. mockに渡すための Controller を生成する
  2. 最後に Controller の Finish() を呼ぶことで、Mock に期待したメソッド呼び出しのチェックをする
  3. Mockに対して EXPECT() の後に、呼ばれることが期待される関数と引数を指定する
  4. Mockに返して欲しい返り値を指定する

その他メモ

  • Mockが用意した EXPECT() メソッドが全て大文字なのは、 interface に定義されるかもしれない Expect() などとの名前衝突を避けるため

  • 上記の例ではMockに対して Bar が引数1つ(99)で呼び出されることを期待している

  • 引数のMatcherはいくつか種類があるが、例えば Bar(99) とそのまま値を渡してもgomock 側でよしなに Bar(gomock.Eq(99) と解釈してくれる

  • Matcher は自作できる

順序について

EXPECT() を複数書いた場合、何もしなければ呼び出し順序までは期待できない。
順序を指定したい場合は AfterInOrder を使う

# Bar1 -> Bar2の順に呼び出されることを期待する
gomock.InOrder(
  m.EXPECT().Bar1(99),
  m.EXPECT().Bar2(100),
)

Reference

https://blog.codecentric.de/en/2017/08/gomock-tutorial/