matryer/moq まとめ

これまで実務でちょいちょい使ってたけど、しっかり調べたことなかったので調べてみた。

# 概要
`go generate` 向けのインターフェースのモックを生成するツール。
任意のインターフェースから構造体を生成し、インターフェースのモックとしてテストコードで使用できる。

# インストール
```
go install github.com/matryer/moq@latest
```

# モックの生成
以下のコマンドを実行してモックを生成する。
```sh
moq [flags] source-dir interface [interface2 [interface3 [...]]]
```

**`source-dir`**
インターフェース定義を記述した Go ファイルが存在するディレクトリへのパス。

**`interface`**
インターフェース名。

## `flags`
**`-fmt string`**
モックのコード出力時に適用する Go の pretty-printer を指定。 
- [`gofmt`](https://qiita.com/WisteriaWave/private/36102a1fecc75ab55698) (デフォルト)
- [`goimports`](https://qiita.com/WisteriaWave/private/4453d51583a55bb22f92) 
- `noop`: 何もしない

:::note info
pretty-printer = 整形表示機能
:::

**`-out string`**
モックの出力先ファイルを指定。デフォルトは標準出力。

**`-pkg string`**
モックのパッケージ名を指定。
デフォルトは推測による決定。

**`-rm`**
出力先にファイルが存在するならば削除してから出力。

**`-skip-ensure`**
モックの実装チェックを抑制し、モックがテストされるパッケージの外側で生成される場合、循環インポートを回避する。

**`-stub`**
モックの実装が提供されていない場合、panic にならず 0 を返す。

**`-version`**
moq コマンドのバージョンを表示。

**`-with-resets`**
モックに対して行われた呼び出しのリセットを容易にする関数を生成する。


# moq コマンドの仕様
公式のオプションの説明だけだと、仕様がよくわからなかったため、ソースコードを読んだり実験して確認してみた。

## モック構造体の名前
インターフェース名をコマンド上で `XxxXxx` と指定するならば、モック構造体名は `XxxXxxMock` に。
`:` を挟んで指定する場合、例えば`XxxXxx:YyyYyy` と指定するならば、モック構造体名は `YyyYyy` に。
つまり、明示的に指定できる。

## モック構造体のパッケージ名
`-pkg` オプションで指定しているならばその値、なければインターフェースのパッケージ名と同名

## `-stub` の効果
フィールド値を置き換えていないモック構造体のメソッドが呼ばれた場合**
- `-stub` オプションを指定している場合: そのメソッドの返り値としてゼロ値を返す
- `-stub` オプションを指定していない場合: panic になる


`-stub` なし
```
func (mock *SampleMock) Now() time.Time {
	if mock.NowFunc == nil {
		panic("SampleMock.NowFunc: method is nil but Sample.Now was just called")
	}
	callInfo := struct {
	}{}
	mock.lockNow.Lock()
	mock.calls.Now = append(mock.calls.Now, callInfo)
	mock.lockNow.Unlock()
	return mock.NowFunc()
}
```

`-stub` あり
```
// Now calls NowFunc.
func (mock *SampleMock) Now() time.Time {
	callInfo := struct {
	}{}
	mock.lockNow.Lock()
	mock.calls.Now = append(mock.calls.Now, callInfo)
	mock.lockNow.Unlock()
	if mock.NowFunc == nil {
		var (
			timeOut time.Time
		)
		return timeOut
	}
	return mock.NowFunc()
}
```

## `-skip-ensure` の効果
「その構造体が特定のインターフェースを実装しているか？」を確認するためによく記述する、
　``var _ <インターフェース名> = &<対象の構造体名> {}``
を出力しないようにする。

これを出力するば、モックのコード出力時に、インターフェースが定義されているファイルの import が必要になる。
結果として、モックを使用したテストで望まない循環参照が発生しやすくなる。

`-skip-ensure` あり:
```go
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq

package mock

import (
	"sync"
	"time"
)

// SampleMock is a mock implementation of sample.Sample.
//
//	func TestSomethingThatUsesSample(t *testing.T) {
//
//		// make and configure a mocked sample.Sample
//		mockedSample := &SampleMock{
//			NowFunc: func() time.Time {
//				panic("mock out the Now method")
//			},
//		}
//
//		// use mockedSample in code that requires sample.Sample
//		// and then make assertions.
//
//	}
type SampleMock struct {
	// NowFunc mocks the Now method.
	NowFunc func() time.Time

	// calls tracks calls to the methods.
	calls struct {
		// Now holds details about calls to the Now method.
		Now []struct {
		}
	}
	lockNow sync.RWMutex
}
```

`-skip-ensure` なし
```golang
// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq

package mock

import (
	"github.com/fnami0316/moq_sample/sample"
	"sync"
	"time"
)

// Ensure, that SampleMock does implement sample.Sample.
// If this is not the case, regenerate this file with moq.
var _ sample.Sample = &SampleMock{}

// SampleMock is a mock implementation of sample.Sample.
//
//	func TestSomethingThatUsesSample(t *testing.T) {
//
//		// make and configure a mocked sample.Sample
...
```

## `-with-resets`
モック構造体は、各メソッドごとにそのメソッドを呼び出したときに、空の構造体がそのメソッド用のスライスに追加されていく挙動になっていて、XxxCalls というメソッドでそのスライスを取得できる。

このオプションを指定することで、各メソッド用のスライスを空にするためのメソッド `ResetXxxCalls` や、すべてのスライスを空にするためのメソッド `ResetCalls` を追加で出力してくれる。

`-with-resets` あり

```go
// ResetNowCalls reset all the calls that were made to Now.
func (mock *SampleMock) ResetNowCalls() {
	mock.lockNow.Lock()
	mock.calls.Now = nil
	mock.lockNow.Unlock()
}

// ResetCalls reset all the calls that were made to all mocked methods.
func (mock *SampleMock) ResetCalls() {
	mock.lockNow.Lock()
	mock.calls.Now = nil
	mock.lockNow.Unlock()
}
```

## `-rm` の効果
公式の説明どおり、モックのファイルを出力する前にファイルを削除するだけ。
別にこのオプションを指定しなくても上書きされる(適当に生成コードにコメント書いたりしてみたが)ため、存在意義がよくわからなかった。


# メソッド挙動の指定
各インターフェースのメソッド `Xxx` の挙動を指定する場合、 モック構造体の `XxxFunc` というフィールドを差し替える。
差し替えなかった場合に、モックを通じてそのメソッドが呼び出されると panic になる(※)。

※先述の通り、`-stub` を指定していれば、そのメソッドのゼロ値の返り値が返ってくるだけ。

# 実例
https://github.com/fnami0316/moq_sample

`sample/sample.go` の Sample インターフェースのモック構造体の定義を `mock/sample.go` に出力させるように、`sample/mock/gen.go` に、`go:generate` ディレクティブを記述してある(オプション全盛りにしてある)

また、生成したモックを使ってテストコードも記述している。

プロジェクトルートで `go generate ./...` を実行するとモックが出力されるので、
`go:generate` ディレクティブ内の moq のオプションを変えたりして実験すると理解が深まるかと思う。

# 参考
https://github.com/matryer/moq