DATA-DOG / go-sqlmock

Sql mock driver for golang to test database interactions
Other
6.09k stars 408 forks source link

gorm (v1.22.4) problem #282

Closed ehduardu closed 3 months ago

ehduardu commented 2 years ago

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

sunfuze commented 2 years ago

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"`
sebastianbuechler commented 2 years ago

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.

dolmen commented 2 years ago

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

j-dubb-dev commented 1 year ago

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.

diegommm commented 3 months ago

Hi @ehduardu! Closing as per @j-dubb-dev's comment, but feel free to reopen if you still have doubts or we can help somehow.

Thank you!