gomockのDoメソッド使えばモックに渡される引数の値をチェックするテストが書ける


# はじめに

[gomock](https://pkg.go.dev/github.com/golang/mock/gomock?tab=doc)の[func (*Call) Do](https://pkg.go.dev/github.com/golang/mock/gomock?tab=doc#Call.Do)メソッドに関してネット上のサンプルコードが少ないという印象だったので本記事を書くことにしました。

# 環境

* go version go1.14.2 darwin/amd64
* github.com/golang/mock v1.4.3

# コード例

__やりたいこと__: ユーザ登録をRepository層としてのインターフェースを通して実行するメソッドのテストを書く。
このとき、構造体の変換処理をするので変換後のDB側構造体が期待するものかをチェックしたい。

## テスト対象のメソッド

```go:app.go
package app

import (
	"database/sql"
)

type IUserRepository interface {
	InsertAUser(user *UserModel) error
}

type UserModel struct {
	ID    uint64         `db:"id"`
	Name  sql.NullString `db:"name"`
	Email string         `db:"email"`
}

type User struct {
	UserID uint32 `json:"user_id"`
	Name   string `json:"user_name"`
	Email  string `json:"user_email"`
}

type userService struct {
	userRepo IUserRepository
}

func (s *userService) RegisterUser(user *User) error {
	var userModel UserModel
	// Mapping process part
	// ID
	userModel.ID = uint64(user.UserID)
	// Name
	if user.Name == "" {
		userModel.Name = sql.NullString{}
	} else {
		userModel.Name = sql.NullString{
			String: user.Name,
			Valid:  true,
		}
	}
	// Email
	userModel.Email = user.Email

	// Call repository func
	err := s.userRepo.InsertAUser(&userModel) // この&userModelの内容のチェックをテストでしたい。
	if err != nil {
		return err
	}

	return nil
}
```

## テストコード with gomock

[gomockのfunc (*Call) Do](https://pkg.go.dev/github.com/golang/mock/gomock?tab=doc#Call.Do)メソッドを利用するとモックに渡される引数を取得できるので`RegisterUser`内でマッピングした結果のDB側構造体のチェックすることができます。

```app_test.go
package app

import (
	"testing"

	"github.com/golang/mock/gomock"
)

//go:generate mockgen -source=app.go -destination=mock.go -package=app

func TestRegisterUser(t *testing.T) {
	// Actual mapped UserModel
	var actUserModel *UserModel

	// Mock
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	userRepoMock := NewMockIUserRepository(mockCtrl)

	userRepoMock.EXPECT().
		InsertAUser(gomock.Any()).
		Do(func(um *UserModel) {
			// Do を使ってモック関数への引数を得ることができる。
			// Do に渡す引数は`actUser`を持つクロージャ関数となる。
			actUserModel = um
		}).
		Return(nil)

	us := &userService{
		userRepo: userRepoMock,
	}

	// Input
	input := User{
		UserID: 123,
		Name:   "John",
		Email:  "john@example.com",
	}

	// Act
	_ = us.RegisterUser(&input)

	// Assert to test mapping
	// マッピング処理の結果をチェック
	if uint32(actUserModel.ID) != input.UserID {
		t.Error()
	}
	if name, _ := actUserModel.Name.Value(); name != input.Name {
		t.Error()
	}
	if actUserModel.Email != input.Email {
		t.Error()
	}
}
```

テストコード内に`go:generate`を記述しているので`go generate`でモックのファイルが生成されます。

```terminal
$ go generate
$ ls mock.go
mock.go
```

テスト

```
$ go test -v
=== RUN   TestRegisterUser
--- PASS: TestRegisterUser (0.00s)
PASS
ok  	github.com/momotaro98/go-codes-for-learning/3rd-parties/gomock	0.057s
```

# 補足 [gomockのfunc (*Call) Do](https://pkg.go.dev/github.com/golang/mock/gomock?tab=doc#Call.Do) の実装を見てみる

https://github.com/golang/mock/blob/3a35fb6e3e18b9dbfee291262260dee7372d2a92/gomock/call.go#L137-L156

```go:gomock/call.go(筆者のコメント付き)
func (c *Call) Do(f interface{}) *Call {
	// 引数関数のリフレクションして型を特定する。
	v := reflect.ValueOf(f)

	c.addAction(func(args []interface{}) []interface{} {
		// テスト実行時に渡ってくる対象モックメソッドへの引数(`args`)も`reflect.Value`を得ることで型を特定する。
		vargs := make([]reflect.Value, len(args))
		ft := v.Type()
		for i := 0; i < len(args); i++ {
			if args[i] != nil {
				vargs[i] = reflect.ValueOf(args[i])
			} else {
				// Use the zero value for the arg.
				vargs[i] = reflect.Zero(ft.In(i))
			}
		}
		// Doメソッドに渡した関数をテスト時に渡ってきた引数を渡して実行する。
		// reflect.Valueにしているのでfが受ける引数の型とvargsの型が一致していなければpanicとなる。
		v.Call(vargs)
		return nil
	})
	return c
}
```