【发布时间】:2021-04-08 07:17:05
【问题描述】:
我不确定为什么我的代码忽略了 select 语句中收到的第一行。代码是:
func (s *sqlserver) FindAllProducts() (products []*Product, err error) {
ctx, cancel := getContext()
defer cancel()
rows, err := s.QueryContext(ctx,
"select productid, productname, pricecents, brandname from products")
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
product := new(Product)
err = rows.Scan(
&product.ProductID,
&product.ProductName,
&product.PriceCents,
&product.BrandName,
)
if err != nil {
return
}
products = append(products, product)
}
return
}
还有一个单元测试,它使用go-sqlmock 来提供一个测试仓库:
func TestProduct(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
require.NoError(t, err)
}
s := sqlserver{db}
defer s.Close()
product1 := &Product{
1,
"soda",
100,
"prites",
}
product2 := &Product{
60,
"slurpee",
400,
"koce",
}
product3 := &Product{
21,
"borg",
210,
"ham",
}
query := "^insert into *"
mock.ExpectExec(query).
WithArgs(product1.ProductID, product1.ProductName, product1.PriceCents, product1.BrandName).
WillReturnResult(sqlmock.NewResult(0, 1))
query = "^insert into *"
mock.ExpectExec(query).
WithArgs(product2.ProductID, product2.ProductName, product2.PriceCents, product2.BrandName).
WillReturnResult(sqlmock.NewResult(1, 1))
query = "^insert into *"
mock.ExpectExec(query).
WithArgs(product3.ProductID, product3.ProductName, product3.PriceCents, product3.BrandName).
WillReturnResult(sqlmock.NewResult(2, 1))
query = "^select .+ from *"
rows := sqlmock.NewRows([]string{"productid", "productname", "pricecents", "brandname"}).
AddRow(product1.ProductID, product1.ProductName, product1.PriceCents, product1.BrandName)
mock.ExpectQuery(query).
WithArgs(product1.ProductID).
WillReturnRows(rows)
query = "^select .+ from *"
rows.AddRow(product2.ProductID, product2.ProductName, product2.PriceCents, product2.BrandName)
rows.AddRow(product3.ProductID, product3.ProductName, product3.PriceCents, product3.BrandName)
mock.ExpectQuery(query).
WillReturnRows(rows)
err = s.InsertProduct(product1)
assert.NoError(t, err)
err = s.InsertProduct(product2)
assert.NoError(t, err)
err = s.InsertProduct(product3)
assert.NoError(t, err)
product, err := s.FindProductFromID(product1.ProductID)
assert.NoError(t, err)
assert.Equal(t, product1, product)
products, err := s.FindAllProducts()
assert.NoError(t, err)
assert.Len(t, products, 3)
assert.Equal(t, *product1, *products[0])
assert.Equal(t, *product2, *products[1])
assert.Equal(t, *product3, *products[2])
err = mock.ExpectationsWereMet()
assert.NoError(t, err)
}
将产品插入表中工作正常,但是当我尝试使用s.FindAllProducts 检索产品时,返回的产品中总是缺少第一个产品(导致assert.Len(t, products, 3) 失败)。非常感谢任何帮助!
编辑:关于在将值扫描到产品中之前调用rows.Next(),这是文档所说的,因为指针应该在第一行之前开始一个索引。但是,我已经调试了代码,问题似乎是指针从前面开始(指向第一行)并调用rows.Next() 将其移动到下一行。我曾尝试在初始化产品后移动此语句,但代码随后会引发错误:
Received unexpected error: sql: Scan called without calling Next
编辑 2:这似乎是 go-sqlmock 的直接问题,因为在真实 SQL 数据库上测试后,数据库正确返回所有 3 行。
【问题讨论】:
-
请说明您的测试到底在哪里失败?
assert.Len(t, products, 3)是否因为只有 2 个产品而失败? -
我不熟悉 Golang,但我猜调用
rows.Next()会将指针移动到第二行。尝试先实例化 Product 并调用rows.Next()作为循环中的最后一件事。 -
请注意,您必须在之前
defer rows.Close()进行错误检查。否则一个错误会导致你的程序崩溃,因为rows将是 nil。 -
另请注意,您的正则表达式
"^insert into *"几乎可以肯定并不意味着您想要的。它的意思是“以insert into开头,后跟0 个或多个空格的字符串”:) 这意味着insert intolerant将匹配,insert into wheee!也将匹配您可能的意思是"^insert into .*",可以缩短为:@987654340 @ -
为什么您的 SELECT 中有
()?,请删除它们以获得结果。