【问题标题】:sqlmock is not matching query, but query is identical and log output shows the samesqlmock 与查询不匹配,但查询相同且日志输出显示相同
【发布时间】:2020-04-26 08:29:26
【问题描述】:

我正在尝试使用 Gorm 使用 sqlmock 为某些代码编写测试。我想为我的 insert 函数编写测试,但现在我正忙着让更新工作。

工作流的第一部分只是从数据库中查询记录。即使日志输出显示它们相同,我也无法让它与我的 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

这确实有效,但现在我遇到了一个新问题。对于初学者来说,Gorm 出于某种原因正在执行 2 个选择语句......第一个工作并找到该行,第二个查询没有找到同一行。我在这里不知所措。即将放弃这个图书馆。在我们努力让它工作的时候,我本可以自己写的。

        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)

【问题讨论】:

    标签: go go-gorm go-sqlmock


    【解决方案1】:

    试试这个:

    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()。

    【讨论】:

    • 这是我在这个主题上能找到的最有用的东西。谢谢!对于大型查询,手动转义查询几乎是不可能的。
    • 最有帮助的答案。谢谢
    • 如果您解释.ExpectQuery() 将字符串解析为正则表达式并需要转义字符,那就太好了
    【解决方案2】:

    mock.ExpectExec() 函数不执行简单的字符串比较。相反,它使用输入字符串作为正则表达式来匹配查询。

    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 不兼容的 DB 架构更改。

    我最终为我的项目设置了这个设置:

    使用mock.ExpectExec()INSERT INTO history 等基本子字符串运行单元测试。这使得测试不那么脆弱。同时我们还在本次测试中进行大量检查,以验证代码执行流程:

    1. SQL 参数数量
    2. 这些 SQL 参数的值
    3. 确保使用 mock.ExpectationsWereMet() 执行 SQL 命令

    除此之外,我们还必须为我们的 SQL 查询运行集成测试。这是确保我们的 SQL 正确且与最新的数据库更改保持同步的唯一方法。

    附:避免在选择中使用*。字段名称要明确。

    更新1:

    注意字符串大小写。 "SELECT" 和 "select" 是两个不同的字符串。

    我当前项目中的一些代码 sn-ps:

    // 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)
    

    【讨论】:

    • 我无法使用“插入历史”方法让它工作。这种方法适用于我的插入测试,但由于某种原因,我无法让它适用于 select 语句。
    • 如何处理 SELECT 语句?我还没有让 ExpectExec 工作。无论我做了多少转义,我都无法让查询匹配。还有如何确认使用 ExpectExec 返回的行,因为它不支持 WillReturnRows。
    • @Crashk1d Reg EX 区分大小写。确保在测试中使用大小写“SELECT”(而不是“select”)
    • 您使用的是什么数据库?我绝对不能让 ExpectQuery("select") 工作。尝试了所有可能的排列,直到我脸色发青。这在我的插入函数测试中效果很好,但在这里不行。我也在使用 Gorm,我认为这是我在这里感到沮丧的原因。在我使用 mysql 和不是最好的 Gorm mysql 驱动程序之间,我在这里遇到了一些问题。 Gorm 在幕后做了很多工作。 sqlmock 的错误消息和日志记录还有很多不足之处。
    • 同意,我已经从 GORM 日志中复制粘贴了确切的查询,但仍然无法匹配。不适用于 MySQL 和 Sqlite3。此时可能只需摆脱整个 gorm 并执行简单的 SQL,因为它真的不值得头疼。
    【解决方案3】:

    这是一个奇怪的解决方案,但对我有用。可能是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])
    

    【讨论】:

      猜你喜欢
      • 2020-12-12
      • 2016-02-22
      • 1970-01-01
      • 2012-07-10
      • 2021-08-01
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 2018-06-17
      相关资源
      最近更新 更多