使用go-sqlmock插件时遇到问题,无法将参数插入到模拟查询中。

huangapple go评论71阅读模式
英文:

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.gorepo_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
	}
}

这里有两个小的改进:

  1. 作为参数传递的*sql.DB。根据最佳实践建议,函数是一等公民。这就是为什么我尽可能坚持使用它们的原因。
  2. 我使用了预处理语句来传递查询的参数,而不是简单的字符串拼接。由于这样做,更容易拦截传递给查询的参数并对其进行设置。

现在让我们切换到测试代码。

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

这里有更多的更改需要注意:

  1. 在实例化dbmock时,你应该将sqlmock.QueryMatcherEqual作为参数传递。这样,它将完全匹配查询。
  2. ExpectQuery方法现在使用了预处理语句功能,并期望一个参数(例如,在这种情况下是tcpdump)。
  3. 重构了断言,以利用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:

  1. 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.
  2. 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:

  1. While instantiating the db and mock, you should pass the sqlmock.QueryMatcherEqual as an argument to. Thanks to this, it'll exactly match queries.
  2. The ExpectQuery method now used the prepared statement feature and expects one argument (e.g. tcpdump in this case).
  3. 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!

huangapple
  • 本文由 发表于 2023年3月16日 04:10:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/75749610.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定