go-sqlmock icon indicating copy to clipboard operation
go-sqlmock copied to clipboard

gorm (v1.22.4) problem

Open ehduardu opened this issue 3 years ago • 4 comments

Hello,

I am having a problem when I working with gorm v2. I have tried every solution and I could find but none worked.

gorm version: 1.22.4 sqlmock version: v1.5.0

This is my code:

package repositories

import (
	"balance-service/src/models"
	"database/sql"
	"log"
	"regexp"

	"github.com/DATA-DOG/go-sqlmock"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

var _ = Describe("Repository", func() {
	var repository *TableTests
	var mock sqlmock.Sqlmock

	BeforeEach(func() {
		var db *sql.DB
		var err error

		db, mock, err = sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
		Expect(err).ShouldNot(HaveOccurred())

		gdb, err := gorm.Open(postgres.New(postgres.Config{
			Conn: db,
		}), &gorm.Config{})
		Expect(err).ShouldNot(HaveOccurred())

		repository = NewTSRepository(gdb)
	})

	AfterEach(func() {
		err := mock.ExpectationsWereMet() // make sure all expectations were met
		Expect(err).ShouldNot(HaveOccurred())
	})

	Context("CreateSubscription", func() {
		var fakeSub *models.TableTest

		BeforeEach(func() {
			fakeSub = &models.TableTest{
				ID:   "123",
				Name: "dudu",
			}
		})

		It("save", func() {
			const query = `
					INSERT INTO "table_test" ("id","name")
						VALUES"`

			mock.MatchExpectationsInOrder(false)
			mock.ExpectBegin()
			mock.ExpectQuery(regexp.QuoteMeta(query)).
				WithArgs("123", "dudu").
				WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))

			mock.ExpectCommit()

			log.Println(repository)

			err := repository.CreateTS(fakeSub)
			Expect(err).ShouldNot(HaveOccurred())
		})
	})

})

The error:

Unexpected error:
      <*fmt.wrapError | 0xc00043fda0>: {
          msg: "call to ExecQuery 'INSERT INTO \"table_tests\" (\"id\",\"name\") VALUES ($1,$2)' with args [{Name: Ordinal:1 Value:123} {Name: Ordinal:2 Value:dudu}] was not expected; call to Rollback transaction was not expected",
          err: <*errors.errorString | 0xc00045dd60>{
              s: "call to Rollback transaction was not expected",
          },
      }
      call to ExecQuery 'INSERT INTO "table_tests" ("id","name") VALUES ($1,$2)' with args [{Name: Ordinal:1 Value:123} {Name: Ordinal:2 Value:dudu}] was not expected; call to Rollback transaction was not expected
  occurred

Thanks

ehduardu avatar Jan 19 '22 01:01 ehduardu

if you usesqlmock.QueryMatcherEqual, may be you should trim space around expect query

const query = `
					INSERT INTO "table_test" ("id","name")
						VALUES"`

to

const query = `INSERT INTO "table_test" ("id","name") VALUES"`

sunfuze avatar Feb 21 '22 03:02 sunfuze

Hello,

I am having a problem when I working with gorm v2. I have tried every solution and I could find but none worked.

gorm version: 1.22.4 sqlmock version: v1.5.0

This is my code:

package repositories

import (
	"balance-service/src/models"
	"database/sql"
	"log"
	"regexp"

	"github.com/DATA-DOG/go-sqlmock"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

var _ = Describe("Repository", func() {
	var repository *TableTests
	var mock sqlmock.Sqlmock

	BeforeEach(func() {
		var db *sql.DB
		var err error

		db, mock, err = sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
		Expect(err).ShouldNot(HaveOccurred())

		gdb, err := gorm.Open(postgres.New(postgres.Config{
			Conn: db,
		}), &gorm.Config{})
		Expect(err).ShouldNot(HaveOccurred())

		repository = NewTSRepository(gdb)
	})

	AfterEach(func() {
		err := mock.ExpectationsWereMet() // make sure all expectations were met
		Expect(err).ShouldNot(HaveOccurred())
	})

	Context("CreateSubscription", func() {
		var fakeSub *models.TableTest

		BeforeEach(func() {
			fakeSub = &models.TableTest{
				ID:   "123",
				Name: "dudu",
			}
		})

		It("save", func() {
			const query = `
					INSERT INTO "table_test" ("id","name")
						VALUES"`

			mock.MatchExpectationsInOrder(false)
			mock.ExpectBegin()
			mock.ExpectQuery(regexp.QuoteMeta(query)).
				WithArgs("123", "dudu").
				WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))

			mock.ExpectCommit()

			log.Println(repository)

			err := repository.CreateTS(fakeSub)
			Expect(err).ShouldNot(HaveOccurred())
		})
	})

})

The error:

Unexpected error:
      <*fmt.wrapError | 0xc00043fda0>: {
          msg: "call to ExecQuery 'INSERT INTO \"table_tests\" (\"id\",\"name\") VALUES ($1,$2)' with args [{Name: Ordinal:1 Value:123} {Name: Ordinal:2 Value:dudu}] was not expected; call to Rollback transaction was not expected",
          err: <*errors.errorString | 0xc00045dd60>{
              s: "call to Rollback transaction was not expected",
          },
      }
      call to ExecQuery 'INSERT INTO "table_tests" ("id","name") VALUES ($1,$2)' with args [{Name: Ordinal:1 Value:123} {Name: Ordinal:2 Value:dudu}] was not expected; call to Rollback transaction was not expected
  occurred

Thanks

I have the same issue. Did you find a solution yet?

Interestingly it seems to be an issue if all fields are provided into the create function or there's something like created_at which is set at DB level via defaults.

package gorm_test

import (
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

type Promoter struct {
	ID   uuid.UUID `json:"id" gorm:"type:uuid"`
	Name string    `json:"name"`
	// CreatedAt time.Time `json:"created_at" gorm:"default:CURRENT_TIMESTAMP"`
}

func TestDB(parentT *testing.T) {
	parentT.Parallel()

	sqlDB, mock, errSQLMock := sqlmock.New()
	assert.Nil(parentT, errSQLMock)

	db, errDB := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
	assert.Nil(parentT, errDB)

	parentT.Run("Promoter", func(t *testing.T) {

		promoterID := uuid.New()
		name := "Promoter"

		// this expects the default transaction to begin
		mock.ExpectBegin()

		// this is the query to be expected
		mock.ExpectQuery(
			regexp.QuoteMeta(`INSERT INTO "promoters" ("id","name") VALUES ($1,$2)`)).
			WithArgs(promoterID, name).WillReturnRows(sqlmock.NewRows([]string{}))

		// this expects the default transaction to commit
		mock.ExpectCommit()

		promoter := Promoter{
			ID:   promoterID,
			Name: name,
		}

		err := db.Debug().Create(&promoter).Error
		assert.Nil(t, err)

		// ensure that all fields were set on the User object
		assert.Equal(t, promoter.ID, promoterID)
		assert.Equal(t, promoter.Name, name)

		// ensure that all expectations are met in the mock
		errExpectations := mock.ExpectationsWereMet()
		assert.Nil(t, errExpectations)
	})
}

Once I uncomment the

CreatedAt time.Time `json:"created_at" gorm:"default:CURRENT_TIMESTAMP"`

it works. And I don't know why that should change anything.

sebastianbuechler avatar Apr 01 '22 08:04 sebastianbuechler

Please post some example code working on the Go Playground. Here is a template.

dolmen avatar Apr 22 '22 22:04 dolmen

Hello, I am having a problem when I working with gorm v2. I have tried every solution and I could find but none worked. gorm version: 1.22.4 sqlmock version: v1.5.0 This is my code:

package repositories

import (
	"balance-service/src/models"
	"database/sql"
	"log"
	"regexp"

	"github.com/DATA-DOG/go-sqlmock"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

var _ = Describe("Repository", func() {
	var repository *TableTests
	var mock sqlmock.Sqlmock

	BeforeEach(func() {
		var db *sql.DB
		var err error

		db, mock, err = sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
		Expect(err).ShouldNot(HaveOccurred())

		gdb, err := gorm.Open(postgres.New(postgres.Config{
			Conn: db,
		}), &gorm.Config{})
		Expect(err).ShouldNot(HaveOccurred())

		repository = NewTSRepository(gdb)
	})

	AfterEach(func() {
		err := mock.ExpectationsWereMet() // make sure all expectations were met
		Expect(err).ShouldNot(HaveOccurred())
	})

	Context("CreateSubscription", func() {
		var fakeSub *models.TableTest

		BeforeEach(func() {
			fakeSub = &models.TableTest{
				ID:   "123",
				Name: "dudu",
			}
		})

		It("save", func() {
			const query = `
					INSERT INTO "table_test" ("id","name")
						VALUES"`

			mock.MatchExpectationsInOrder(false)
			mock.ExpectBegin()
			mock.ExpectQuery(regexp.QuoteMeta(query)).
				WithArgs("123", "dudu").
				WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))

			mock.ExpectCommit()

			log.Println(repository)

			err := repository.CreateTS(fakeSub)
			Expect(err).ShouldNot(HaveOccurred())
		})
	})

})

The error:

Unexpected error:
      <*fmt.wrapError | 0xc00043fda0>: {
          msg: "call to ExecQuery 'INSERT INTO \"table_tests\" (\"id\",\"name\") VALUES ($1,$2)' with args [{Name: Ordinal:1 Value:123} {Name: Ordinal:2 Value:dudu}] was not expected; call to Rollback transaction was not expected",
          err: <*errors.errorString | 0xc00045dd60>{
              s: "call to Rollback transaction was not expected",
          },
      }
      call to ExecQuery 'INSERT INTO "table_tests" ("id","name") VALUES ($1,$2)' with args [{Name: Ordinal:1 Value:123} {Name: Ordinal:2 Value:dudu}] was not expected; call to Rollback transaction was not expected
  occurred

Thanks

I have the same issue. Did you find a solution yet?

Interestingly it seems to be an issue if all fields are provided into the create function or there's something like created_at which is set at DB level via defaults.

package gorm_test

import (
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

type Promoter struct {
	ID   uuid.UUID `json:"id" gorm:"type:uuid"`
	Name string    `json:"name"`
	// CreatedAt time.Time `json:"created_at" gorm:"default:CURRENT_TIMESTAMP"`
}

func TestDB(parentT *testing.T) {
	parentT.Parallel()

	sqlDB, mock, errSQLMock := sqlmock.New()
	assert.Nil(parentT, errSQLMock)

	db, errDB := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
	assert.Nil(parentT, errDB)

	parentT.Run("Promoter", func(t *testing.T) {

		promoterID := uuid.New()
		name := "Promoter"

		// this expects the default transaction to begin
		mock.ExpectBegin()

		// this is the query to be expected
		mock.ExpectQuery(
			regexp.QuoteMeta(`INSERT INTO "promoters" ("id","name") VALUES ($1,$2)`)).
			WithArgs(promoterID, name).WillReturnRows(sqlmock.NewRows([]string{}))

		// this expects the default transaction to commit
		mock.ExpectCommit()

		promoter := Promoter{
			ID:   promoterID,
			Name: name,
		}

		err := db.Debug().Create(&promoter).Error
		assert.Nil(t, err)

		// ensure that all fields were set on the User object
		assert.Equal(t, promoter.ID, promoterID)
		assert.Equal(t, promoter.Name, name)

		// ensure that all expectations are met in the mock
		errExpectations := mock.ExpectationsWereMet()
		assert.Nil(t, errExpectations)
	})
}

Once I uncomment the

CreatedAt time.Time `json:"created_at" gorm:"default:CURRENT_TIMESTAMP"`

it works. And I don't know why that should change anything.

When you set defaults with the gorm struct tag, gorm does some things that you don't always expect. (https://github.com/go-gorm/gorm/blob/master/callbacks/create.go#L207-L295). The order of struct fields and the columns in the sql statement don't always line up, if the struct fields is nil it won't be passed as an argument to the query, and depending on your dialect it may change from an exec (requires sqlmock.ExpectExec) to a query (requires sqlmock.ExpectQuery) and the sql statement might have extra clauses added to it. (in sqlserver a default fields adds OUTPUT INSERTED."column_with_default_field" to the sql statement). If you're using the regex matcher, try using a broad match like mock.ExpectQuery(INSERT INTO "promoters" (.+) VALUES (.+)) WITHOUT the regexp.QuoteMeta (because (.+) literally matches on 1 or more of any character. If that matches then try mock.ExpectQuery(regexp.QuoteMeta(INSERT INTO "promoters" ("id","name") VALUES ($1,$2)))

Also, don't use regexp.QuoteMeta with the sqlmock.QueryMatcherEqual matcher. That could also be the reason because QuoteMeta with quote your ()'s and that will never match exactly with the actual query.

j-dubb-dev avatar Apr 13 '23 03:04 j-dubb-dev