質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.31%
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Q&A

解決済

2回答

1821閲覧

Golangのunitテストで、transactionのエラーが発生する。

hajimeeeee

総合スコア3

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

0グッド

0クリップ

投稿2023/09/20 09:50

編集2023/09/21 12:34

実現したいこと

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

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ikedas

2023/09/20 12:53

関数 TestSaveRefreshToken()のコードはどのようなものでしょうか。
hajimeeeee

2023/09/20 14:20

記載するのを忘れていました… 該当のソースコードの項目に追記しました。
ikedas

2023/09/21 02:17

GoRMのバージョンも、わかれば記載していただけますか。
ikedas

2023/09/21 02:43

どうもNewUserRepository()があやしい気がするのですが、これのコードはどのようなものでしょうか。この中でCreateやSaveをしているのではないですか。
hajimeeeee

2023/09/21 12:21

gormのバージョンはv1.25.2です。 NewUserRepository()も記載しました。 func NewUserRepository(db *gorm.DB) IUserRepository { return &userRepository{db} }
guest

回答2

0

自己解決

user_repository.go のgorm の書き方に誤りがありました。
最終的にはExpectExec()を使いこのような形になりました。

golang

1// user_repository_test.go 2func TestSaveRefreshToken(t *testing.T) { 3 t.Run("正常系:リフレッシュトークンを保存できる", func(t *testing.T) { 4 user := &model.User{ 5 ID: 2, 6 RefreshToken: "refreshtoken", 7 } 8 database, gormDB, mock, err := NewDbMock() 9 defer database.Close() 10 assert.NoError(t, err) 11 mock.ExpectBegin() 12 mock.ExpectExec(regexp.QuoteMeta(`UPDATE "users" SET "refresh_token"=$1,"updated_at"=$2 WHERE id=$3 AND "users"."deleted_at" IS NULL AND "id" = $4`)). 13 WithArgs(user.RefreshToken, AnyTime{}, user.ID, user.ID). 14 WillReturnResult(sqlmock.NewResult(0, 1)) 15 mock.ExpectCommit() 16 ur := NewUserRepository(gormDB) 17 err = ur.SaveRefreshToken(user, user.ID, user.RefreshToken) 18 assert.NoError(t, err) 19 assert.NoError(t, mock.ExpectationsWereMet()) 20 }) 21}

golang

1//user_repository.go 2func (ur *userRepository) SaveRefreshToken(user *model.User, userId uint, refreshToken string) error { 3 result := ur.db.Model(user).Where("id=?", userId).Update("refresh_token", refreshToken) 4 if result.Error != nil { 5 return API.Errorf(code.InternalServer, "failed to save refresh_token by internal server error: %s", result.Error) 6 } 7 if result.RowsAffected < 1 { 8 return API.Errorf(code.NoRows, "failed to save refresh_token by no rows") 9 } 10 return nil 11}

投稿2023/09/22 13:21

hajimeeeee

総合スコア3

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ikedas

2023/09/23 05:08

.db.Model(user) とするべきところが .db.Updates(user) になっていたということですね。
guest

0

#1

ご質問のエラーと同じと思われる報告が上がっています:

これによると、GORMでPostgreSQLダイアレクトを用いる場合、INSERT文の実行にはExec()ではなくQueryRow()が用いられるため、sqlmockでのテストにはExpectExec()ではなくExpectQuery()を用いる必要があるとのことです (PostgreSQL以外、たとえばSQLiteダイアレクトを用いる場合は通常通りExec()が用いられますからExpectExec()でテストできます)。

現在ご提示のコードにはUPDATE文の実行 (Update()) しか見当たりませんが、ユーザリポジトリを作成する際にINSERT文の実行 (Save()Create()) が行われているのではないかと考えます。

#2

TestUpdateUser()は期待通り動作しているとのことですが、

func TestUpdateUser(t *testing.T) { ... mock.ExpectBegin() 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 *`)). WithArgs(user.Email, user.ImgPath, AnyTime{}, user.ID, user.ID). WillReturnRows(sqlmock.NewRows([]string{"email"}).AddRow(user.Email)) mock.ExpectCommit() ...

となっており、UPDATE文に対してExpectQuery()を用いておられます。

ということで、#1で述べたことはINSERT文だけでなくUPDATE文にもあてはまるのではないでしょうか。つまり、問題の箇所でもExpectExec()の代わりにExpectQuery()を使えば問題は解消するように思われます。

投稿2023/09/21 03:09

編集2023/09/22 04:02
ikedas

総合スコア4443

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hajimeeeee

2023/09/21 12:44

ありがとうございます。 NewUserRepositoryでは、Save()やCreate()は行っていないです。 func NewUserRepository(db *gorm.DB) IUserRepository { return &userRepository{db} } また、今回のTestSaveRefreshToken以外のテストケース(TestGetUserByEmail・TestCreateUser・TestUpdateUser)はテストが通っている状態です。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.31%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問