はじめに
近年WEBアプリケーションのバックエンドAPIにGolangを採用することが増えている。それに伴いGORMをORマッパーに採用することも少なくないだろう。
本記事ではデータベースにPostgresを採用時、GORMで実装したInfrastructure層のCreate関数を単体テストする。
Domain層
Infrastructure層の単体テストを行うにあたり、本記事で利用するリソースの抜粋を以下に示す。
type User struct {
gorm.Model
Name string `gorm:"not null"`
Email string `gorm:"uniqueIndex;not null"`
}
type UserRepository interface {
Create(*gorm.DB, *User) error
}
Infrastructure層
今回単体テストを行うCreate関数を以下に示す。
type userPersistence struct {}
func (up userPersistence) Create(db *gorm.DB, user *domain.User) error {
return db.Create(user).Error
}
単体テスト
本記事の目的である単体テストを行うプログラムを以下に示す。
type AnyTime struct{}
func (a AnyTime) Match(v driver.Value) bool {
_, ok := v.(time.Time)
return ok
}
func TestCreateUser(t *testing.T) {
user := &domain.User{
Name: "Testing"
Email: "test@sample.com"
}
sqlDB, mock, err := sqlmock.New()
if err != nil {
t.Errorf(err.Error())
}
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
if err != nil {
t.Errorf(err.Error())
}
mock.ExpectBegin()
mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users"`)).
WithArgs(AnyTime{}, AnyTime{}, nil, user.Name, user.Email).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
mock.ExpectCommit()
p := NewUserPersistence()
if err := p.Create(db, user); err != nil {
t.Errorf(err.Error())
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf(err.Error())
}
}
ここで注目したいのがmock.ExpectQuery
である。go-sqlmockのGitHub内Readmeに、INSERT INTOのテストはmock.ExpectExec
で行うと記載されている。本来、INSERT, UPDATE, DELETEなどのsqlはExec、selectなどのsqlはQueryで実行される。簡単に説明すると、Execでの実行は戻り値がなく、Queryでの実行は戻り値がある、と考えて問題ない。
ではなぜINSERTなのにQueryで実装されるのか。これはDBがPostgresの際にGORMが作成するsqlにRETURNING
が含まれるからである。GORMが作成するsqlを以下に示す。
INSERT INTO "users" ("created_at","updated_at","deleted_at","name","email") VALUES ($1,$2,$3,$4,$5) RETURNING "id"
RETURNINGはPostgresのみの機能(MySQLにはない)で、実行時に戻り値を設定する。GORMのCreate関数が作成するsqlはIDを返すようになっているため、Execでの実行ではなくQueryでの実行になり、mock.ExpectQuery
でテストを行う必要がある。これはgo-sqlmockのissueで言及されている。
まとめ
本記事ではPostgresのDBを利用時GORMで実装したCreate関数を、go-sqlmockで単体テストを行う手法をまとめた。詳しいtestingやgo-sqlmockの記載方法、DataBaseのExecとQueryの違いなどは公式サイトを確認してほしい。
では、良いエンジニアライフを。