✔️
GORMとgo-txdbで、DBを使ったユニットテストを安全に実行する
この記事でわかること
go-txdbを使ってgormでDBの読み書きを行うテストを安全に実行できるようにします。
概要
DB読み書きを伴う自動テストを記述するとき、あるテストが別のテストで作成されたテストデータの影響を受けてテストが失敗してしまうことがあります。
go-txdbを使うと、テストケース(コネクション)ごとにテストデータを分離できるので、テストケース間でテストデータが汚染されることを気にする必要がなくなったり、テスト終了後にテストデータの削除を気にする必要がなくなります。
テスト用にインメモリのsqliteを使用します。
本来はMySQLやPostgreSQLのように永続的にインスタンスが存在するDBで利用するとメリットがわかりやすいです。
登場するライブラリ
コード
ポイントだけ書きます。
gormインスタンスを作成する前にtxdb.Registerを呼び出しておく。
テストごとにコネクションを作成してテストを実行する。
nameはテストデータを分離してほしい単位(今回はテストの関数ごと)でユニークにしておく。
sqlite.NewのDriverNameとしてnameを渡すとtxdb経由でsqliteが呼び出されるようになる。
MySQL/PostgreSQLでも同様にするとテストがトランザクション内で実行される。
func NewTestDB(name string) (*gorm.DB, error) {
txdb.Register(name, "sqlite3", dsn)
dialector := sqlite.New(sqlite.Config{
DriverName: name,
DSN: dsn,
})
db, err := gorm.Open(dialector, &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return nil, err
}
initDB(db)
return db, nil
}
各テストの頭でDB接続する。
deferでcloseを忘れずに。
後は普通にテストを記述する。
テストが複数あってもテストデータは混ざらないし、テスト終了後にテスト中で作成したデータは削除される。
func TestLoginUser(t *testing.T) {
db, _ := NewTestDB("TestLoginUser")
defer func() {
db, _ := db.DB()
db.Close()
}()
db.Create(
&usecase.User{
Name: "test1",
Email: "test1@example.com",
Password: "test1",
},
)
type args struct {
email string
password string
}
tests := []struct {
name string
args args
want *usecase.User
wantErr bool
}{
{
name: "normal",
args: args{
email: "test1@example.com",
password: "test1",
},
want: &usecase.User{
Name: "test1",
Email: "test1@example.com",
Password: "test1",
},
wantErr: false,
},
{
name: "exists",
args: args{
email: "test@example.com",
password: "test",
},
want: nil,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
got, err := usecase.LoginUser(db, tt.args.email, tt.args.password)
if (err != nil) != tt.wantErr {
t.Errorf("LoginUser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.want == nil {
assert.Nil(got)
return
}
assert.Equal(got.Name, tt.want.Name)
assert.Equal(got.Email, tt.want.Email)
assert.Equal(got.Password, tt.want.Password)
})
}
}
Discussion