0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

matryer/moq まとめ

Last updated at Posted at 2025-02-22

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

概要

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

インストール

go install github.com/matryer/moq@latest

モックの生成

以下のコマンドを実行してモックを生成する。

moq [flags] source-dir interface [interface2 [interface3 [...]]]

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

interface
インターフェース名。

flags

-fmt string
Go の pretty-printer を指定。

  • gofmt: (デフォルト)
  • goimports:
  • noop: 何もしない

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 あり:

// 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 なし

// 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 あり

// 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 を指定していれば、そのメソッドのゼロ値の返り値が返ってくるだけ。

実例

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

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

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

参考

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?