#mysql #sql #go
# #mysql #sql #Вперед
Вопрос:
Я не уверен, почему мой код пропускает первую строку, полученную в операторе 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(
amp;product.ProductID,
amp;product.ProductName,
amp;product.PriceCents,
amp;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 := amp;Product{
1,
"soda",
100,
"prites",
}
product2 := amp;Product{
60,
"slurpee",
400,
"koce",
}
product3 := amp;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 строки.
Комментарии:
1. Пожалуйста, уточните, где именно ваш тест терпит неудачу?
assert.Len(t, products, 3)
Сбой, потому что есть только 2 продукта?2. Я не знаком с Golang, но я предполагаю, что вызов
rows.Next()
перемещает указатель на вторую строку. Попробуйте сначала создать экземпляр Product и вызватьrows.Next()
его последним в цикле.3.Обратите внимание, что вы должны выполнить проверку ошибок раньше
defer rows.Close()
. В противном случае ошибка приведет к панике вашей программы, потомуrows
что будет равно нулю.4. Также обратите внимание, что ваше регулярное
"^insert into *"
выражение почти наверняка не означает то, что вы намереваетесь. Это означает «строка, начинающаяся сinsert into
, за которой следует 0 или более пробелов» 🙂 Это означает, чтоinsert intolerant
будет соответствовать, какinsert into wheee!
Вы, вероятно, имели в виду"^insert into .*"
, что может быть сокращено до:^insert into "
5. Почему у вас есть
()
в вашем SELECT?, Пожалуйста, удалите их, чтобы получить результаты.
Ответ №1:
Обнаружена ошибка, поскольку rows — указатель по какой-то причине go-sqlmock
изменяет набор строк, в результате чего исходные строки в нем удаляются и требуют сброса при каждом WillReturnRows
вызове. Следующая часть кода:
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)
должно быть изменено, чтобы добавить исходную строку обратно, например:
query = "^select . from *"
rows = sqlmock.NewRows([]string{"productid", "productname", "pricecents", "brandname"})
rows.AddRow(product1.ProductID, product1.ProductName, product1.PriceCents, product2.BrandName)
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)
и тест теперь пройден!