简体   繁体   中英

How to use go-sqlmock WithArgs() with a variable number of arguments?

I use go-sqlmock ( https://godoc.org/github.com/DATA-DOG/go-sqlmock ) to test a function that receives a variable number of arguments (I simplified the function for the sake of simplicity and stripped out most of the code):

func getHits(db *sql.DB, actions ...string) (hits []Hit, err error) {
   // ...
   query := `select * from table where action in (?,?)`
   rows, err := db.Query(query, actions)
   // ...
}

The tests look like that:

// rows := ...
actions := []string{"click", "event"}
mock.ExpectQuery(`^select .*`).WithArgs(actions).WillReturnRows(rows)
hits, err := getHits(db, actions...)
if mockErr := mock.ExpectationsWereMet(); mockErr != nil {
    log.Fatalf("there were unfulfilled expections: %s", mockErr)
}

Then I get this output:

2017/12/21 10:38:23 there were unfulfilled expections: there is a remaining expectation which was not matched: ExpectedQuery => expecting Query or QueryRow which:
- matches sql: '^select .*'
- is with arguments:
  0 - [click event]
- should return rows: ...

If I change the tests like this:

mock.ExpectQuery(`^select .*`).WithArgs(actions[0], actions[1]).WillReturnRows(rows)

Then I get this output:

2017/12/21 10:44:41 there were unfulfilled expections: there is a remaining expectation which was not matched: ExpectedQuery => expecting Query or QueryRow which:
- matches sql: '^select .*'
- is with arguments:
  0 - click
  1 - event
- should return rows:

The only I can make it pass is by calling :

db.Query(query, actions[0], actions[1])

which is what I obviously don't want to do as I don't know the number of actions...

Does anyone have an idea on how I could fix or debug this ?

Accepted answer using []interface{} would work for string but it can throw an error for other types.

Short answer

actions := []driver.Value{"click", "event"}
mock.ExpectQuery(`^select .*`).WithArgs(actions...).WillReturnRows(rows)

Long answer

The argument requires driver.Value reference and driver.Value can be one of these types :

  • int64
  • float64
  • bool
  • []byte
  • string
  • time.Time

so, the correct answer is

actions := []driver.Value{"click", "event"}
mock.ExpectQuery(`^select .*`).WithArgs(actions...).WillReturnRows(rows)

I found a way to fix my problem : If I convert the slice of strings to a slice of interfaces for db.Query, it works just fine:

boundValues := make([]interface{}, len(actions))

for i, val := range actions {
    boundValues[i] = val
}

rows, err := db.Query(query, boundValues...)

Then for the tests:

mock.ExpectQuery(`^select .*`).WithArgs(actions[0], actions[1]).WillReturnRows(rows)

NOTE: passing just db.Query(query, actions...) does not work; this results in cannot use actions (type []string) as type []interface {} in argument to db.Query

A much cleaner solution would be to use this.

/* Prepare database mock */
type AnyNumber struct{}

// Match satisfies sqlmock.Argument interface
func (a AnyNumber) Match(v driver.Value) bool {
    _, ok := v.(float64)
    return ok
}

And then use AnyNumber{} as the argument in WithArgs

Here is the reference in the docs Click Here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM