sqlmock не соответствует запросу, но запрос идентичен, и вывод журнала показывает то же самое
Я пытаюсь написать тесты для некоторого кода, используя Gorm, используя sqlmock. Я придумал, как писать тесты для моей функции вставки, но теперь выдергиваю волосы, пытаясь заставить обновление работать.
Первая часть рабочего процесса просто запрашивает запись из базы данных. Я не могу заставить его соответствовать моему SQL, хотя в выводе журнала они отображаются как идентичные.
Вот сообщение об ошибке:
(/path/to/my/project/database.go:263)
[2020-01-08 10:29:40] Query: could not match actual sql: "SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC LIMIT 1" with expected regexp "SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC LIMIT 1"
Я также пробовал использовать вставку ExpectExec из ExpectQuery.
for _, c := range cases {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
DB, err := gorm.Open("sqlite3", db)
if err != nil {
t.Fatal(err)
}
DB.LogMode(true)
mock.ExpectQuery(`SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC LIMIT 1`)
err = UpdateStoragePool(DB, &c.givenPool)
if !reflect.DeepEqual(c.wantedError, err) {
t.Fatalf("expecting errror %q, got %q", c.wantedError, err)
}
// if we didn't have any errors during the tx, check all expectations were met
if c.wantedError == nil {
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf(err.Error())
}
}
}
Я также пробовал:
mock.ExpectQuery(`SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = '1')) ORDER BY "storage_pools"."id" ASC LIMIT 1`).WithArgs(1)
mock.ExpectExec(`SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC LIMIT 1`)
mock.ExpectExec(`SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = '1')) ORDER BY "storage_pools"."id" ASC LIMIT 1`).WithArgs(1)
У кого-нибудь есть идеи, что я здесь делаю не так?
* ОБНОВИТЬ *
По какой-то причине это НЕ работает для операторов select:
mock.ExpectExec(`SELECT \* FROM "storage_pools"`).
WithArgs(c.givenPool.PoolId).WillReturnResult(sqlmock.NewResult(1, 1))
[2020-01-13 10:32:21] call to Query 'SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC LIMIT 1' with args [{Name: Ordinal:1 Value:1}], was not expected, next expectation is: ExpectedExec => expecting Exec or ExecContext which:
- matches sql: 'SELECT \* FROM "storage_pools"'
- is with arguments:
0 - 1
- should return Result having:
LastInsertId: 1
RowsAffected: 1
Это РАБОТАЕТ, но теперь я столкнулся с новой проблемой, где. Для начала, Горм по какой-то причине выполняет 2 оператора select... Первый работает и находит строку, второй запрос не находит ту же строку. Я здесь в растерянности. Собираюсь просто отказаться от этой библиотеки. Я мог бы написать свой собственный за то время, которое мы потратили на то, чтобы заставить его работать.
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
if err != nil {
t.Fatal(err)
}
DB, err := gorm.Open("postgres", db)
if err != nil {
t.Fatal(err)
}
DB.LogMode(true)
mockedRow := sqlmock.NewRows([]string{"id", "created_at", "updated_at", "poolid"}).AddRow(1, time.Now(), time.Now(), "1")
// Mock the complete transaction
mock.ExpectQuery(`SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC LIMIT 1`).
WithArgs(c.givenPool.PoolId).
WillReturnRows(mockedRow)
mock.ExpectQuery(`SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND "storage_pools"."id" = ? AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC`).
WithArgs(1, c.givenPool.PoolId).
WillReturnRows(mockedRow)
3 ответа
Попробуй это:
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC LIMIT 1`))
Поместите свой запрос в эту функцию regexp.QuoteMeta().
mock.ExpectExec()
функция не выполняет простое сравнение строк. Вместо этого он использует входную строку как RegExp для соответствия запросу.
Некоторые символы в строке соответствия SQL являются зарезервированными символами RegExp и должны быть экранированы, чтобы соответствовать SQL.
Ваша строка должна выглядеть так после экранирования:
SELECT \* FROM "storage_pools" WHERE "storage_pools"\."deleted_at" IS NULL AND \(\(poolid \= \?\)\) ORDER BY "storage_pools"\."id" ASC LIMIT 1
Подсказка: вы можете скрыть свою строку в Интернете, используя https://www.regex-escape.com/preg_quote-online.php или какой-либо другой сайт
Дополнительная мысль: Тест с точным совпадением SQL может быть ненадежным без добавления особой дополнительной ценности для точного SQL.
Тест может дать ложноположительный результат, если кто-то внес в него безобидные изменения, например, добавив лишний пробел. С другой стороны, при полнотекстовом сопоставлении не обнаруживаются изменения схемы БД, несовместимые с SQL.
Я закончил с этой настройкой для своих проектов:
Запускайте модульные тесты с помощью mock.ExpectExec()
с основными подстроками, такими как INSERT INTO history
. Это делает тесты менее хрупкими. В то же время мы все еще много проверяем в этом тесте, чтобы проверить поток выполнения кода:
- Количество параметров SQL
- Значения этих параметров SQL
- Убедитесь, что команда SQL выполняется с использованием mock.ExpectationsWereMet()
Вдобавок к этому мы должны запустить интеграционные тесты для наших SQL-запросов. Это единственный способ убедиться, что наш SQL правильный и актуален с учетом последних изменений в БД.
PS Избегайте *
в избранном. Будьте ясны с именами полей.
Обновление1:
Будьте осторожны с футляром для струн. "SELECT" и "select" - две разные строки.
Некоторые фрагменты кода из моего текущего проекта:
// insert
sqlMock.ExpectExec("INSERT INTO eeo").
WithArgs("2018-12-31", "John Dow", "title"}).
WillReturnResult(sqlmock.NewResult(mock.EeoID, 1))
// select
rows := sqlmock.NewRows([]string{"req_id", "state"})
sqlMock.ExpectQuery("select").WithArgs(mock.CandidateID).WillReturnRows(rows)
Это странное решение, но у меня сработало. Вероятно ошибка в sqlmock. Дублируйте переменную mockedRow и вставьте их в ExpectQuery.
mockedRow := sqlmock.NewRows([]string{"id", "created_at", "updated_at", "poolid"}).AddRow(1, time.Now(), time.Now(), "1")
mockedRow2 := sqlmock.NewRows([]string{"id", "created_at", "updated_at", "poolid"}).AddRow(1, time.Now(), time.Now(), "1")
// Mock the complete transaction
mock.ExpectQuery(`SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC LIMIT 1`).
WithArgs(c.givenPool.PoolId).
WillReturnRows(mockedRow)
mock.ExpectQuery(`SELECT * FROM "storage_pools" WHERE "storage_pools"."deleted_at" IS NULL AND "storage_pools"."id" = ? AND ((poolid = ?)) ORDER BY "storage_pools"."id" ASC`).
WithArgs(1, c.givenPool.PoolId).
WillReturnRows(mockedRow2)
В качестве альтернативы вы можете создать массив mockedRow следующим образом:
mockedRow := []*sqlmock.Rows{
sqlmock.NewRows([]string{"id", "created_at", "updated_at", "poolid"}).AddRow(1, time.Now(), time.Now(), "1"),
sqlmock.NewRows([]string{"id", "created_at", "updated_at", "poolid"}).AddRow(1, time.Now(), time.Now(), "1"),
}
And use it as WillReturnRows(mockedRow[0]) and WillReturnRows(mockedRow[1])