英文:
Issue using go-sqlmock and inserting arguments into mock queries
问题
我正在尝试使用go-sqlmock来模拟我的查询函数,并尝试复制数据库表的行为。然而,我得不到我期望的结果。查询的行为不像应该的那样,参数没有被插入到查询中,实际结果也是错误的。我在这里做错了什么?
这是我正在模拟的函数和查询:
func (y *YumDatabase) getTransactionId(pkg string) (int, error) {
var id int
queryForTid := "SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install " + pkg + "%' ORDER BY tid DESC LIMIT 1"
row := y.db.QueryRow(queryForTid)
switch err := row.Scan(&id); err {
case sql.ErrNoRows:
fmt.Println("No rows were returned")
return 0, err
case nil:
return id, nil
default:
return 0, err
}
}
这是模拟测试函数:
func TestGetTransactionId(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("err not expected: %v", err)
}
pkg := "tcpdump"
rows := sqlmock.NewRows([]string{"tid"}).AddRow("1").AddRow("3")
mock.ExpectQuery("SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install " + pkg + "%' ORDER BY tid DESC LIMIT 1").WillReturnRows(rows)
mockdb := &YumDatabase{
db: db,
}
got, err := mockdb.getTransactionId("tcpdump")
assert.Equal(t, 3, got)
}
如果上述代码按预期工作,我将在got
中得到'3',但实际上我得到的是'1'。
其次,是否可以将Rows更改为以下内容:
rows := sqlmock.NewRows([]string{"tid", "cmdline"}).AddRow("1", "install test").AddRow("3", "delete test2")
并且实际执行类似于WHERE cmdline LIKE '%install XYZ%'
的比较,因为我尝试过这样做,但是得到了以下错误(主要代码包括查询都构建和工作正常,所以我猜这是我编写的模拟代码的问题):
error sql: expected 2 destination arguments in Scan, not 1
我期望从SQL查询中返回最高的tid,而不是在"AddRow"中指定的第一个tid,并且我希望查询能够检查模拟数据中的"cmdline"行。
英文:
I am trying to mock my query functions using go-sqlmock & replicate the database table similarly. However, I am not getting the results that I expect. The query is not behaving as it should, arguments are not being inserted into the query & the actual result is incorrect. What am I doing wrong here?
This is the function & the query I am mocking:
func (y *YumDatabase) getTransactionId(pkg string) (int, error) {
var id int
queryForTid := "SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install " + pkg + "%' ORDER BY tid DESC LIMIT 1"
row := y.db.QueryRow(queryForTid)
switch err := row.Scan(&id); err {
case sql.ErrNoRows:
fmt.Println("No rows were returned")
return 0, err
case nil:
return id, nil
default:
return 0, err
}
}
And this is the mock test function:
func TestGetTransactionId(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("err not expected: %v", err)
}
pkg := "tcpdump"
rows := sqlmock.NewRows([]string{"tid"}).AddRow("1").AddRow("3")
mock.ExpectQuery("SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install " + pkg + "%' ORDER BY tid DESC LIMIT 1").WillReturnRows(rows)
mockdb := &YumDatabase{
db: db,
}
got, err := mockdb.getTransactionId("tcpdump")
assert.Equal(t, 3, got)
}
If the above worked as expected, I would get back '3' in 'got' but instead I get back '1'
Secondly, is it possible to change Rows to the following:
rows := sqlmock.NewRows([]string{"tid", "cmdline"}).AddRow("1", "install test").AddRow("3", "delete test2")
And actually do the comparison "WHERE cmdline LIKE '%install XYZ%'", because I tried this and I got back the following error (all of the main code builds & works including the queries, so this is an issue with the mock code I've wrote I'm guessing):
error sql: expected 2 destination arguments in Scan, not 1
I expect to see the highest tid returned from the SQL query, not the first one specified in "AddRow", and I expect the query to implement a check of the "cmdline" row from the mocks.
答案1
得分: 2
我用以下方式满足了你的要求。首先,让我分享代码,然后我会逐步介绍所有相关的更改。代码包含在两个文件中:repo.go
和repo_test.go
。
repo.go
文件
package repo
import (
"database/sql"
"fmt"
)
func GetTransactionId(db *sql.DB, pkg string) (int, error) {
var id int
row := db.QueryRow("SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install $1%' ORDER BY tid DESC LIMIT 1", pkg)
switch err := row.Scan(&id); err {
case sql.ErrNoRows:
fmt.Println("未返回任何行")
return 0, err
case nil:
return id, nil
default:
return 0, err
}
}
这里有两个小的改进:
- 作为参数传递的
*sql.DB
。根据最佳实践建议,函数是一等公民。这就是为什么我尽可能坚持使用它们的原因。 - 我使用了预处理语句来传递查询的参数,而不是简单的字符串拼接。由于这样做,更容易拦截传递给查询的参数并对其进行设置。
现在让我们切换到测试代码。
repo_test.go
文件
package repo
import (
"database/sql"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestGetTransactionId(t *testing.T) {
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
if err != nil {
t.Fatalf("在打开模拟数据库时出现意外错误,%v", err)
}
t.Run("正常情况", func(t *testing.T) {
rows := sqlmock.NewRows([]string{"tid"}).AddRow("1")
mock.ExpectQuery("SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install $1%' ORDER BY tid DESC LIMIT 1").
WithArgs("tcpdump").
WillReturnRows(rows)
got, err := GetTransactionId(db, "tcpdump")
assert.Equal(t, 1, got)
assert.Nil(t, err)
})
t.Run("未返回任何行", func(t *testing.T) {
mock.ExpectQuery("SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install $1%' ORDER BY tid DESC LIMIT 1").
WithArgs("tcpdump").
WillReturnError(sql.ErrNoRows)
got, err := GetTransactionId(db, "tcpdump")
assert.Equal(t, 0, got)
assert.Equal(t, sql.ErrNoRows, err)
})
}
这里有更多的更改需要注意:
- 在实例化
db
和mock
时,你应该将sqlmock.QueryMatcherEqual
作为参数传递。这样,它将完全匹配查询。 ExpectQuery
方法现在使用了预处理语句功能,并期望一个参数(例如,在这种情况下是tcpdump
)。- 重构了断言,以利用
github.com/stretchr/testify/assert
包的功能。
希望这能帮助你解决问题,如果有任何问题,请告诉我!
英文:
I managed your requirement in this way. First, let me share the code, then, I'll walk you through all of the relevant changes. The code is contained in two files: repo.go
and repo_test.go
.
repo.go
file
package repo
import (
"database/sql"
"fmt"
)
func GetTransactionId(db *sql.DB, pkg string) (int, error) {
var id int
row := db.QueryRow("SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install $1%' ORDER BY tid DESC LIMIT 1", pkg)
switch err := row.Scan(&id); err {
case sql.ErrNoRows:
fmt.Println("No rows were returned")
return 0, err
case nil:
return id, nil
default:
return 0, err
}
}
Here, there are two small improvements:
- The
*sql.DB
passed in as a parameter. As suggested by the best practices, Functions are first-class citizens. That's why I prefer to stick to them whenever possible. - I used the prepared statement to pass in the argument of the query. Not a simple string concatenation. Thanks to this, it's easier to intercept the arguments passed to the query and set up expectations over them
Now let's switch to the test code.
repo_test.go
file
package repo
import (
"database/sql"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestGetTransactionId(t *testing.T) {
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
if err != nil {
t.Fatalf("err not expected while opening mock db, %v", err)
}
t.Run("HappyPath", func(t *testing.T) {
rows := sqlmock.NewRows([]string{"tid"}).AddRow("1")
mock.ExpectQuery("SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install $1%' ORDER BY tid DESC LIMIT 1").
WithArgs("tcpdump").
WillReturnRows(rows)
got, err := GetTransactionId(db, "tcpdump")
assert.Equal(t, 1, got)
assert.Nil(t, err)
})
t.Run("NoRowsReturned", func(t *testing.T) {
mock.ExpectQuery("SELECT tid FROM trans_cmdline WHERE cmdline LIKE '%install $1%' ORDER BY tid DESC LIMIT 1").
WithArgs("tcpdump").
WillReturnError(sql.ErrNoRows)
got, err := GetTransactionId(db, "tcpdump")
assert.Equal(t, 0, got)
assert.Equal(t, sql.ErrNoRows, err)
})
}
Here, there are more changes that you need to be aware of:
- While instantiating the
db
andmock
, you should pass thesqlmock.QueryMatcherEqual
as an argument to. Thanks to this, it'll exactly match queries. - The
ExpectQuery
method now used the prepared statement feature and expects one argument (e.g.tcpdump
in this case). - Refactored the assertions to take advantage of the
github.com/stretchr/testify/assert
package.
I hope that this helps you in solving your issue, let me know!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论