実現したいこと
Golangのunitテストのエラーを解決したい。
前提
golangでクリーンアーキテクチャに基づいて実装しています。
repository層の中の、SaveRefreshTokenというrefresh tokenをDBに保存するメソッドのunitテストでエラーが発生しています。
ちなみに、このメソッド以外のunitテストは成功しているので、テストの設定等には問題はなさそうです。
発生している問題・エラーメッセージ
GOROOT=/opt/homebrew/Cellar/go/1.20.2/libexec #gosetup GOPATH=/Users/haji/go #gosetup /opt/homebrew/Cellar/go/1.20.2/libexec/bin/go test -c -o /private/var/folders/_5/z7_l4csd659802b3j17jlvqc0000gn/T/GoLand/___TestSaveRefreshToken__uReserve_repository__.test uReserve/repository #gosetup /opt/homebrew/Cellar/go/1.20.2/libexec/bin/go tool test2json -t /private/var/folders/_5/z7_l4csd659802b3j17jlvqc0000gn/T/GoLand/___TestSaveRefreshToken__uReserve_repository__.test -test.v -test.paniconexit0 -test.run ^\QTestSaveRefreshToken\E$ === RUN TestSaveRefreshToken --- FAIL: TestSaveRefreshToken (0.00s) === RUN TestSaveRefreshToken/正常系:リフレッシュトークンを保存できる user_repository_test.go:123: Error Trace: /Users/haji/Desktop/Deveropment/udemy/uReserve/repository/user_repository_test.go:123 Error: Received unexpected error: Code: internal_server, Msg: failed to save refresh_token by internal server error: all expectations were already fulfilled, call to database transaction Begin was not expected; invalid transaction Test: TestSaveRefreshToken/正常系:リフレッシュトークンを保存できる --- FAIL: TestSaveRefreshToken/正常系:リフレッシュトークンを保存できる (0.00s) FAIL プロセス が終了コード 1 で終了しました
該当のソースコード
golang
1//user_repository_test.go 2 3var storedUser = &model.User{ 4 ID: 1, 5 Name: "田中太郎", 6 Email: "tanaka@test.com", 7 Password: "password", 8 ImgPath: "tanaka.png", 9 RefreshToken: "refreshtoken", 10 CreatedAt: time.Now(), 11 UpdatedAt: time.Now(), 12 DeletedAt: gorm.DeletedAt{}, 13} 14 15func TestGetUserByEmail(t *testing.T) { 16 t.Run("正常系:Emailからユーザー情報を取得できる", 17 func(t *testing.T) { 18 database, gormDB, mock, err := NewDbMock() 19 defer database.Close() 20 assert.NoError(t, err) 21 // SQL、引数、戻り値が意図したものであることを期待する 22 mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE email=$1 AND "users"."deleted_at" IS NULL ORDER BY "users"."id" LIMIT 1`)). 23 WithArgs(storedUser.Email). 24 WillReturnRows(sqlmock.NewRows([]string{"id", "name", "email", "password", "img_path"}).AddRow(storedUser.ID, storedUser.Name, storedUser.Email, storedUser.Password, storedUser.ImgPath)) 25 26 ur := NewUserRepository(gormDB) 27 user := model.User{} 28 err = ur.GetUserByEmail(&user, storedUser.Email) 29 assert.NoError(t, err) 30 assert.Equal(t, user.ID, storedUser.ID) 31 assert.Equal(t, user.Name, storedUser.Name) 32 assert.Equal(t, user.Email, storedUser.Email) 33 assert.Equal(t, user.Password, storedUser.Password) 34 assert.Equal(t, user.ImgPath, storedUser.ImgPath) 35 assert.NoError(t, mock.ExpectationsWereMet()) 36 }) 37 38 t.Run("異常系:存在しないEmailでエラーを返す", 39 func(t *testing.T) { 40 database, gormDB, mock, err := NewDbMock() 41 defer database.Close() 42 assert.NoError(t, err) 43 44 mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE email=$1 AND "users"."deleted_at" IS NULL ORDER BY "users"."id" LIMIT 1`)). 45 WithArgs("tanaka@test.com").WillReturnError(gorm.ErrRecordNotFound) 46 ur := NewUserRepository(gormDB) 47 user := model.User{} 48 err = ur.GetUserByEmail(&user, storedUser.Email) 49 assert.EqualError(t, err, "Code: no_rows, Msg: failed to find user: record not found") 50 }) 51 52 t.Run("異常系:サーバーエラーを返す", 53 func(t *testing.T) { 54 database, gormDB, mock, err := NewDbMock() 55 defer database.Close() 56 assert.NoError(t, err) 57 58 mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users" WHERE email=$1 AND "users"."deleted_at" IS NULL ORDER BY "users"."id" LIMIT 1`)). 59 WithArgs("tanaka@test.com").WillReturnError(gorm.ErrInvalidDB) 60 ur := NewUserRepository(gormDB) 61 user := model.User{} 62 err = ur.GetUserByEmail(&user, storedUser.Email) 63 assert.EqualError(t, err, "Code: internal_server, Msg: find user failed by internal server error: invalid db") 64 }) 65} 66 67func TestCreateUser(t *testing.T) { 68 t.Run("正常系:ユーザー情報を登録できる", 69 func(t *testing.T) { 70 user := &model.User{ 71 Name: "齊藤元", 72 Email: "insert@test.com", 73 Password: "password", 74 ImgPath: "saito.png", 75 Role: 9, 76 RefreshToken: "", 77 CreatedAt: time.Now(), 78 UpdatedAt: time.Now(), 79 DeletedAt: gorm.DeletedAt{}, 80 } 81 82 database, gormDB, mock, err := NewDbMock() 83 defer database.Close() 84 assert.NoError(t, err) 85 mock.ExpectBegin() 86 mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO "users" ("name","email","password","img_path","role","refresh_token","deleted_at","created_at","updated_at") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING "created_at","updated_at","id"`)). 87 WithArgs(user.Name, user.Email, user.Password, user.ImgPath, user.Role, user.RefreshToken, user.DeletedAt, user.CreatedAt, user.UpdatedAt). 88 WillReturnRows(sqlmock.NewRows([]string{"created_at", "updated_at", "id"}).AddRow(user.CreatedAt, user.UpdatedAt, 2)) 89 mock.ExpectCommit() 90 ur := NewUserRepository(gormDB) 91 err = ur.CreateUser(user) 92 assert.NoError(t, err) 93 }) 94} 95 96func TestUpdateUser(t *testing.T) { 97 t.Run("正常系:ユーザー情報を更新できる", 98 func(t *testing.T) { 99 user := &model.User{ 100 ID: 1, 101 Email: "update@test.com", 102 ImgPath: "update.png", 103 } 104 database, gormDB, mock, err := NewDbMock() 105 defer database.Close() 106 assert.NoError(t, err) 107 mock.ExpectBegin() 108 mock.ExpectQuery(regexp.QuoteMeta(`UPDATE "users" SET "email"=$1,"img_path"=$2,"updated_at"=$3 WHERE id=$4 AND "users"."deleted_at" IS NULL AND "id" = $5 RETURNING *`)). 109 WithArgs(user.Email, user.ImgPath, AnyTime{}, user.ID, user.ID). 110 WillReturnRows(sqlmock.NewRows([]string{"email"}).AddRow(user.Email)) 111 mock.ExpectCommit() 112 113 ur := NewUserRepository(gormDB) 114 err = ur.UpdateUser(user, user.ID, user.ImgPath) 115 assert.NoError(t, err) 116 assert.NoError(t, mock.ExpectationsWereMet()) 117 }) 118 119} 120 121func TestSaveRefreshToken(t *testing.T) { 122 t.Run("正常系:リフレッシュトークンを保存できる", func(t *testing.T) { 123 user := &model.User{ 124 DeletedAt: gorm.DeletedAt{}, 125 ID: 2, 126 RefreshToken: "refreshtoken", 127 UpdatedAt: time.Now(), 128 } 129 _, gormDB, mock, err := NewDbMock() 130 assert.NoError(t, err) 131 mock.ExpectBegin() 132 mock.ExpectExec(regexp.QuoteMeta(`UPDATE "users" SET "refresh_token"=$1,"updated_at"=$2 WHERE "users"."deleted_at" IS NULL AND "id" = $3`)). 133 WithArgs(user.RefreshToken, AnyTime{}, user.ID). 134 WillReturnResult(sqlmock.NewResult(0, 1)) // RowsAffectedが1を返すように修正 135 mock.ExpectCommit() 136 ur := NewUserRepository(gormDB) 137 err = ur.SaveRefreshToken(user, user.ID, user.RefreshToken) 138 assert.NoError(t, err) 139 assert.NoError(t, mock.ExpectationsWereMet()) 140 }) 141}
golang
1//user_repository.go 2 3package repository 4 5type IUserRepository interface { 6 GetUserByEmail(user *model.User, email string) error 7 CreateUser(user *model.User) error 8 UpdateUser(user *model.User, userId uint, path string) error 9 SaveRefreshToken(user *model.User, userId uint, refreshToken string) error 10} 11 12type userRepository struct { 13 db *gorm.DB 14} 15 16func NewUserRepository(db *gorm.DB) IUserRepository { 17 return &userRepository{db} 18} 19 20func (ur *userRepository) GetUserByEmail(user *model.User, email string) error { 21 if err := ur.db.Where("email=?", email).First(&user).Error; err != nil { 22 if errors.Is(err, gorm.ErrRecordNotFound) { 23 return API.Errorf(code.NoRows, "failed to find user: %s", err) 24 } 25 return API.Errorf(code.InternalServer, "find user failed by internal server error: %s", err) 26 } 27 fmt.Print("user: ", user) 28 return nil 29} 30 31func (ur *userRepository) CreateUser(user *model.User) error { 32 if err := ur.db.Create(user).Error; err != nil { 33 var pgErr *pgconn.PgError 34 if errors.As(err, &pgErr) { 35 if pgErr.Code == pgerrcode.UniqueViolation || pgErr.Code == pgerrcode.ForeignKeyViolation { 36 return API.Errorf(code.Forbidden, "create user failed by forbidden error: %s", err) 37 } 38 } 39 return API.Errorf(code.InternalServer, "create user failed by internal server error: %s", err) 40 } 41 fmt.Print("user: ", user) 42 return nil 43} 44 45func (ur *userRepository) SaveRefreshToken(user *model.User, userId uint, refreshToken string) error { 46 result := ur.db.Updates(user).Where("id=?", userId).Update("refresh_token", refreshToken) 47 if result.Error != nil { 48 return API.Errorf(code.InternalServer, "failed to save refresh_token by internal server error: %s", result.Error) 49 } 50 if result.RowsAffected < 1 { 51 return API.Errorf(code.NoRows, "failed to save refresh_token by no rows") 52 } 53 return nil 54} 55 56func (ur *userRepository) UpdateUser(user *model.User, userId uint, path string) error { 57 result := ur.db.Model(&user).Clauses(clause.Returning{}).Where("id=?", userId).Updates(model.User{Name: user.Name, Email: user.Email, ImgPath: path}) 58 if result.Error != nil { 59 return API.Errorf(code.InternalServer, "failed to update user by internal server error: %s", result.Error) 60 } 61 if result.RowsAffected < 1 { 62 return API.Errorf(code.NoRows, "failed to update user by no rows") 63 } 64 return nil 65} 66
golang
1//test_config.go 2 3package repository 4 5type AnyTime struct{} 6 7// Match satisfies sqlmock.Argument interface 8func (a AnyTime) Match(v driver.Value) bool { 9 _, ok := v.(time.Time) 10 return ok 11} 12 13func NewDbMock() (*sql.DB, *gorm.DB, sqlmock.Sqlmock, error) { 14 database, mock, err := sqlmock.New() 15 gormDB, err := gorm.Open( 16 postgres.New( 17 postgres.Config{ 18 Conn: database, 19 }), &gorm.Config{}) 20 return database, gormDB, mock, err 21} 22
補足情報(FW/ツールのバージョンなど)
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/stretchr/testify v1.8.4
testing
gorm.io/driver/postgres v1.5.2
gorm.io/gorm v1.25.2
関数 TestSaveRefreshToken()のコードはどのようなものでしょうか。
記載するのを忘れていました…
該当のソースコードの項目に追記しました。
GoRMのバージョンも、わかれば記載していただけますか。
どうもNewUserRepository()があやしい気がするのですが、これのコードはどのようなものでしょうか。この中でCreateやSaveをしているのではないですか。
gormのバージョンはv1.25.2です。
NewUserRepository()も記載しました。
func NewUserRepository(db *gorm.DB) IUserRepository {
return &userRepository{db}
}

回答2件
あなたの回答
tips
プレビュー