📘

sqlmock + gormでDB周りのテストを書く

2022/05/10に公開

sqlmockとは

Goのテスト用のDBを用意しなくてもSQLドライバのような振る舞いをしてくれるモックのライブラリ

パッケージのバージョン

go.mod
gorm.io/driver/postgres v1.3.4 // indirect
gorm.io/gorm v1.23.1 // indirect

困ったこと

gorm.io/driver/mysqlのv1.3.2では正しく動作しない。v1.0.3では正しく動作する。
Where("id=?", 1) ではなく Where("id = ?", 1) を記述する。

https://github.com/Watson-Sei/go-sqlmock-gorm

解決フロー

このような困った仕様に気づくために記事を見るだけでなく実際に動くコードをGitHubからcloneしてきてコードを色々変えて試してみることがとても大切だと感じた。Versionも確認すること。

実際のコード

package database_test

import (
	"fmt"
	"regexp"
	"testing"
	"time"
	"github.com/DATA-DOG/go-sqlmock"
	"github.com/ymktmk/golang-clean-architecture/domain"
	"github.com/ymktmk/golang-clean-architecture/infrastructure"
	"github.com/ymktmk/golang-clean-architecture/interfaces/database"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

func NewDbMock() (*gorm.DB, sqlmock.Sqlmock, error) {
	sqlDB, mock, err := sqlmock.New()
	mockDB, err := gorm.Open(postgres.New(postgres.Config{
		Conn: sqlDB,
	}), &gorm.Config{})
	return mockDB, mock, err
}

func DummyHandler(conn *gorm.DB) database.SqlHandler {
	sqlHandler := new(infrastructure.SqlHandler)
	sqlHandler.Conn = conn
	return sqlHandler
}

func TestStore(t *testing.T) {
	mockDB, mock, err := NewDbMock()
	if err != nil {
		t.Errorf("Failed to initialize mock DB: %v", err)
	}
	
	u := &domain.User{
		Name: "sheep",
		Email: "example@gmail.com",
	}
	
	// mock設定
	rows := sqlmock.NewRows([]string{"id"}).AddRow(1)

	mock.ExpectBegin()
	mock.ExpectQuery(regexp.QuoteMeta(
		`INSERT INTO "users" ("created_at","updated_at","deleted_at","name","email") VALUES ($1,$2,$3,$4,$5)`)).
		WillReturnRows(rows)
	mock.ExpectCommit()

	// repository 初期化
	repo := &database.UserRepository{SqlHandler: DummyHandler(mockDB)}
	user, err := repo.Store(u)
	fmt.Println(user)
	if err != nil {
		t.Fatal(err)
	}
	
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("Test Create User: %v", err)
	}
}


func TestFindById(t *testing.T) {
	mockDB, mock, err := NewDbMock()
	if err != nil {
		t.Errorf("Failed to initialize mock DB: %v", err)
	}

	// mock設定
	rows := sqlmock.NewRows([]string{"id", "name", "email", "created_at", "updated_at", "deleted_at"}).
	AddRow(1, "tomoki", "example@gmail.com", time.Now(), time.Now(), nil)
	
	mock.ExpectQuery(regexp.QuoteMeta(
		`SELECT * FROM "users" WHERE id = $1`)).
		WithArgs(1).
		WillReturnRows(rows)

      // repository 初期化
	repo := &database.UserRepository{SqlHandler: DummyHandler(mockDB)}
	user, err := repo.FindById(1)
	fmt.Println(user)
	if err != nil {
		t.Fatal(err)
	}

	if err := mock.ExpectationsWereMet(); err != nil {
		t.Errorf("Test Find User: %v", err)
	}
}

マイリポジトリ

動くものをサンプルとして公開しています。よかったら試してみてください。

https://github.com/ymktmk/gorm-sqlmock.git

参考文献

https://qiita.com/seya/items/582c2bdcca4ad50b03b7

https://qiita.com/gold-kou/items/cb174690397f651e2d7f

https://qiita.com/otanu/items/761de2bfc38468e9d353

https://tech.fusic.co.jp/posts/2020-12-02-mock-gormdb-using-go-sqlmock/

Discussion