GoのテストでDBをモックする

# はじめに
こんにちは、H×Hのセンリツ大好きエンジニアです。（同担OKです😉）

今回はGoにおいてデータベースを扱う関数のテストを行うためにDBのモックを用意する所と、実際に使用したテストを紹介していきます！

# DBをモックしてテストする
### 使用するライブラリ
`github.com/DATA-DOG/go-sqlmock`を使用します。

https://github.com/DATA-DOG/go-sqlmock

こちらは、SQLドライバのような振る舞いをしてくれるモックを生成してくれるライブラリです。
モックを使うことで、テスト用のDBを用意しなくても良くなります。

### 今回テストする関数
ユーザをEmailから検索するという関数（`UserByEmail`）を用意しました。
この関数は、usersテーブルからクエリとして受け取ったEmailが存在するかチェックする関数となっています。
``` user.go
package persistence

import (
	"backend/domain/model"
	"backend/domain/repository"

	"gorm.io/gorm"
)

type userPersistence struct {
	db *gorm.DB
}

func NewUserPersistence(db *gorm.DB) repository.UserRepository {
	return &userPersistence{db}
}

func (up *userPersistence) UserByEmail(user *model.User, email string) error {
	if err := up.db.Where("email=?", email).First(user).Error; err != nil {
		return err
	}
	return nil
}
```

### テストコード
``` user_test.go
func TestUserPersistence_UserByEmail(t *testing.T) {
	t.Run(
		"異常系：コネクションエラー",
		func(t *testing.T) {
			db, _, err := sqlmock.New()
			assert.NoError(t, err)

			email := "test@example.com"

			up := NewUserPersistence(db)
			user := model.User{}
			err = up.UserByEmail(&user, email)

			assert.Error(t, err)
		},
	)
	t.Run(
		"正常系：ユーザーを正常に取得できた場合",
		func(t *testing.T) {
			db, mock, err := sqlmock.New()
			assert.NoError(t, err)

			layout := "2006-01-02 15:04:06"
			createdAtExample := "2006-01-02 15:04:06"
			time, _ := time.Parse(layout, createdAtExample)

			email := "test@example.com"
			mockUser := model.User{
				ID:        1,
				Email:     email,
				Password:  "securepassword123",
				CreatedAt: time,
				UpdatedAt: time,
			}

			// データベースからの期待されるクエリと戻り値の設定
			rows := sqlmock.NewRows([]string{"id", "email", "password", "created_at", "updated_at"}).
				AddRow(mockUser.ID, mockUser.Email, mockUser.Password, mockUser.CreatedAt, mockUser.UpdatedAt)

			mock.ExpectQuery(
				regexp.QuoteMeta("SELECT * FROM `users` WHERE email=? ORDER BY `users`.`id` LIMIT ?"),
			).WithArgs(email, 1).WillReturnRows(rows)

			up := NewUserPersistence(db)
			user := &model.User{}
			err = up.UserByEmail(user, email)

			assert.NoError(t, err)
			assert.Equal(t, mockUser.ID, user.ID)
			assert.Equal(t, mockUser.Email, user.Email)
			assert.NoError(t, mock.ExpectationsWereMet())
		},
	)
}

```

サブテスト化して異常系と正常系のテストを作成していますが、重要なのは以下の部分です。
``` user_test.go
db, mock, err := sqlmock.New()
```
`sqlmock.New()`を使用することで、モックを作成します。
これにより、テスト用にDBを作らなくともモックでDBのテストが行えます。

異常系の中身を見てみましょう。

``` user_test.go
t.Run(
	"異常系：コネクションエラー",
	func(t *testing.T) {
		db, _, err := sqlmock.New()
		assert.NoError(t, err)

		email := "test@example.com"

		up := NewUserPersistence(db)
		user := model.User{}
		err = up.UserByEmail(&user, email)

		assert.Error(t, err)
	},
)
```

ここではモッククエリを使用しないため、`mock`を`_`にしています。
そのあとは、関数の呼び出しを行いエラーが出ているかチェックします。

次に、正常系をみてみましょう。
``` user_test.go
t.Run(
	"正常系：ユーザーを正常に取得できた場合",
	func(t *testing.T) {
		db, mock, err := sqlmock.New()
		assert.NoError(t, err)

		layout := "2006-01-02 15:04:06"
		createdAtExample := "2006-01-02 15:04:06"
		time, _ := time.Parse(layout, createdAtExample)

		email := "test@example.com"
		mockUser := model.User{
			ID:        1,
			Email:     email,
			Password:  "securepassword123",
			CreatedAt: time,
			UpdatedAt: time,
		}

		// データベースからの期待されるクエリと戻り値の設定
		rows := sqlmock.NewRows([]string{"id", "email", "password", "created_at", "updated_at"}).
			AddRow(mockUser.ID, mockUser.Email, mockUser.Password, mockUser.CreatedAt, mockUser.UpdatedAt)

		mock.ExpectQuery(
			regexp.QuoteMeta("SELECT * FROM `users` WHERE email=? ORDER BY `users`.`id` LIMIT ?"),
		).WithArgs(email, 1).WillReturnRows(rows)

		up := NewUserPersistence(db)
		user := &model.User{}
		err = up.UserByEmail(user, email)

		assert.NoError(t, err)
		assert.Equal(t, mockUser.ID, user.ID)
		assert.Equal(t, mockUser.Email, user.Email)
		assert.NoError(t, mock.ExpectationsWereMet())
	},
)
```

ここでは先ほどと違い、モッククエリと期待される返り値を記述します。
具体的には以下の部分です。
``` user_test.go
// データベースからの期待されるクエリと戻り値の設定
		rows := sqlmock.NewRows([]string{"id", "email", "password", "created_at", "updated_at"}).
			AddRow(mockUser.ID, mockUser.Email, mockUser.Password, mockUser.CreatedAt, mockUser.UpdatedAt)

		mock.ExpectQuery(
			regexp.QuoteMeta("SELECT * FROM `users` WHERE email=? ORDER BY `users`.`id` LIMIT ?"),
		).WithArgs(email, 1).WillReturnRows(rows)
```

上記のコードでは、
- `sqlmock.NewRows`でデータベースから返される行を作成し、必要なフィールドを設定
- `mock.ExpectQuery`でクエリと引数を指定し、設定した行を返すように設定

を行っています。

CRUDのRを行う場合は`mock.ExpectQuery`を使用しますが、 CUDでは期待する返り値をモックする`mock.ExpectExec`を使います。
詳しくはこちらをご覧ください。

https://pkg.go.dev/github.com/data-dog/go-sqlmock

このようにDBに送信するクエリと返り値をモックに定義してあげると、実際にDBに接続しているような動作が保証されます。
後は正常系のテストですので、エラーを起こさないかであったり、返り値が期待通りか確認しています。

これで、DBをモックしてテストを作成することが出来ました。

# おわりに
今回はDBをモックしてテストする場合の簡単な内容について触れました！
ですが、最近だとテスト用のDBを作成する方が良いと言った声を耳にするので、どちらの手法が正解かは分かりません。😇

どちらにもメリデメは存在するのでどちらの方が良いかは悩みどころですね🤔

最後までご覧いただきありがとうございました！
以上、センリツでした。🤓