Gorm使用带有clauses的delete方法进行sqlmock测试。

huangapple go评论79阅读模式
英文:

Gorm delete with clauses sqlmock test

问题

我有一个带有返回结果的 Gorm 删除操作:

expirationDate := time.Now().UTC().Add(-(48 * time.Hour))
var deletedUsers Users
res := gormDB.WithContext(ctx).
	Table("my_users").
	Clauses(clause.Returning{Columns: []clause.Column{{Name: "email"}}}).
	Where("created_at < ?", expirationDate).
	Delete(&deletedUsers)

现在使用 clauses 的测试总是失败。例如:

sqlMock.ExpectExec(`DELETE`)
    .WithArgs(expirationDate)
    .WillReturnResult(sqlmock.NewResult(1, 1))

收到错误信息:

"call to Query 'DELETE FROM "my_users" WHERE created_at < $1 RETURNING "email"' with args [{Name: Ordinal:1 Value:2023-01-18 06:15:34.694274 +0000 UTC}], was not expected, next expectation is: ExpectedExec => expecting Exec or ExecContext which:

  • matches sql: 'DELETE'
  • is with arguments:
    0 - 2023-01-18 06:15:34.694274 +0000 UTC
  • should return Result having:
    LastInsertId: 1
    RowsAffected: 1"

我尝试了许多其他的 sqlMock 期望,但它们都有类似的问题。
另外,在 ExpectExec 中我们没有返回值,只有在 ExpectQuery 中有返回值...
有没有人有办法测试带有 Clauses 的 Gorm 查询?

英文:

I have a Gorm delete with the returning result:

expirationDate := time.Now().UTC().Add(-(48 * time.hour))
var deletedUsers Users
res := gormDB.WithContext(ctx).
	Table(&quot;my_users&quot;).
	Clauses(clause.Returning{Columns: []clause.Column{{Name: &quot;email&quot;}}}).
	Where(&quot;created_at &lt; ?&quot;, expirationDate).
	Delete(&amp;deletedUsers)

Now the test with clauses always fails. e.g. :

sqlMock.ExpectExec(`DELETE`)
    .WithArgs(expirationDate)
    .WillReturnResult(sqlmock.NewResult(1, 1))

Receiving error:

"call to Query 'DELETE FROM &quot;my_users&quot; WHERE created_at < $1 RETURNING &quot;email&quot;' with args [{Name: Ordinal:1 Value:2023-01-18 06:15:34.694274 +0000 UTC}], was not expected, next expectation is: ExpectedExec => expecting Exec or ExecContext which:\n - matches sql: 'DELETE'\n - is with arguments:\n 0 - 2023-01-18 06:15:34.694274 +0000 UTC\n - should return Result having:\n LastInsertId: 1\n RowsAffected: 1"

I tried many other sqlMock expectations, but they have a similar issue.
Also, we don't have a return value in ExpectExec, only in ExpectQuery...
Any chance someone has to test the Gorm query with the Clauses?

答案1

得分: 1

我已经成功处理了你所需的内容。首先,让我分享我写的文件,然后我会逐步介绍所有相关的更改。文件repo.go用于生产环境,repo_test.go用于测试代码。

repo.go

package gormdelete

import (
	"context"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type Users struct {
	Email string
}

func Delete(ctx context.Context, gormDB *gorm.DB) error {
	expirationDate := time.Now().UTC().Add(-(48 * time.Hour))

	var deletedUsers Users
	res := gormDB.WithContext(ctx).Table("my_users").Clauses(clause.Returning{Columns: []clause.Column{{Name: "email"}}}).Where("created_at < ?", expirationDate).Delete(&deletedUsers)
	if res.Error != nil {
		return res.Error
	}
	return nil
}

由于你没有提供完整的文件,我尽量猜测了缺失的部分。

repo_test.go

package gormdelete

import (
	"context"
	"database/sql/driver"
	"testing"
	"time"

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

// 这段代码直接从文档中复制而来
// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
type AnyTime struct{}

// Match 满足 sqlmock.Argument 接口
func (a AnyTime) Match(v driver.Value) bool {
	_, ok := v.(time.Time)
	return ok
}

func TestDelete(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("不应该出现错误: %v", err)
	}

	conn, _ := db.Conn(context.Background())
	gormDb, err := gorm.Open(postgres.New(postgres.Config{
		Conn: conn,
	}))

	row := sqlmock.NewRows([]string{"email"}).AddRow("test@example.com")
	mock.ExpectBegin()
	mock.ExpectQuery("DELETE FROM \"my_users\" WHERE created_at < ?").WithArgs(AnyTime{}).WillReturnRows(row)
	mock.ExpectCommit()

	err = Delete(context.Background(), gormDb)

	assert.Nil(t, err)
	if err = mock.ExpectationsWereMet(); err != nil {
		t.Errorf("未满足所有期望: %v", err)
	}
}

这里还有一些值得注意的更改:

  1. 我按照文档实例化了AnyTime(你可以在注释中看到链接)。
  2. 再次,我猜测了dbmockgormDb的设置,但我认为它们应该差不多。
  3. 我将ExpectExec的使用方式改为ExpectQuery,因为我们将得到一个结果集,这是根据你的repo.go文件中的Clauses方法指定的。
  4. 你需要在ExpectQuery之前加上ExpectBeginExpectCommit
  5. 最后,注意驱动程序在SQL语句中参数的期望方式的差异。在生产代码中,你可以选择使用?$1。然而,在测试代码中,你只能使用?,否则它将无法匹配期望。

希望能有所帮助,如果还有其他问题,请告诉我!

英文:

I was able to successfully manage what you need. First, let me share the files I wrote, and then I'll walk you through all of the relevant changes. The files are repo.go for production and repo_test.go for the test code.

repo.go

package gormdelete

import (
	&quot;context&quot;
	&quot;time&quot;

	&quot;gorm.io/gorm&quot;
	&quot;gorm.io/gorm/clause&quot;
)

type Users struct {
	Email string
}

func Delete(ctx context.Context, gormDB *gorm.DB) error {
	expirationDate := time.Now().UTC().Add(-(48 * time.Hour))

	var deletedUsers Users
	res := gormDB.WithContext(ctx).Table(&quot;my_users&quot;).Clauses(clause.Returning{Columns: []clause.Column{{Name: &quot;email&quot;}}}).Where(&quot;created_at &lt; ?&quot;, expirationDate).Delete(&amp;deletedUsers)
	if res.Error != nil {
		return res.Error
	}
	return nil
}

As you didn't provide the full file I tried to guess what was missing.

repo_test.go

package gormdelete

import (
	&quot;context&quot;
	&quot;database/sql/driver&quot;
	&quot;testing&quot;
	&quot;time&quot;

	&quot;github.com/DATA-DOG/go-sqlmock&quot;
	&quot;github.com/stretchr/testify/assert&quot;
	&quot;gorm.io/driver/postgres&quot;
	&quot;gorm.io/gorm&quot;
)

// this is taken directly from the docs
// https://github.com/DATA-DOG/go-sqlmock#matching-arguments-like-timetime
type AnyTime struct{}

// Match satisfies sqlmock.Argument interface
func (a AnyTime) Match(v driver.Value) bool {
	_, ok := v.(time.Time)
	return ok
}

func TestDelete(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf(&quot;an error was not expected: %v&quot;, err)
	}

	conn, _ := db.Conn(context.Background())
	gormDb, err := gorm.Open(postgres.New(postgres.Config{
		Conn: conn,
	}))

	row := sqlmock.NewRows([]string{&quot;email&quot;}).AddRow(&quot;test@example.com&quot;)
	mock.ExpectBegin()
	mock.ExpectQuery(&quot;DELETE FROM \&quot;my_users\&quot; WHERE created_at &lt; ?&quot;).WithArgs(AnyTime{}).WillReturnRows(row)
	mock.ExpectCommit()

	err = Delete(context.Background(), gormDb)

	assert.Nil(t, err)
	if err = mock.ExpectationsWereMet(); err != nil {
		t.Errorf(&quot;not all expectations were met: %v&quot;, err)
	}
}

Here, there are more changes that it's worth mentioning:

  1. I instantiated the AnyTime as per the documentation (you can see the link in the comment).
  2. Again, I guessed the setup of the db, mock, and gormDb but I think it should be more or less the same.
  3. I switch the usage of ExpectExec to ExpectQuery as we'll have back a result set as specified by the Clauses method in your repo.go file.
  4. You've to wrap the ExpectQuery within an ExpectBegin and an ExpectCommit.
  5. Finally, pay attention to the difference in how the driver expects the parameters in the SQL statement. In the production code, you can choose to use ? or $1. However, in the test code, you can only use the ? otherwise it won't match the expectations.

Hope to help a little bit, otherwise, let me know!

huangapple
  • 本文由 发表于 2023年1月18日 14:21:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/75155333.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定