简介
sqlmock是一个实现sql/driver的模拟库。它有一个唯一的目的:在测试中模拟任何sql驱动程序行为,而无需真正的数据库连接。它有助于维护正确的TDD工作流程。
- 该库现在是完整且稳定的。(由于这个原因,您可能找不到新的更改)
- 支持并发和多个连接。
- 支持go1.8上下文相关的功能模拟和命名sql参数。
- 不需要对源代码进行任何修改。
- 该驱动程序允许模拟任何sql驱动程序方法行为。
- 默认情况下具有严格的期望顺序匹配。
- 没有第三方依赖性。
注意:在v1.2.0中, sqlmock.Rows
已从接口更改为struct
,如果您使用对该接口的任何类型引用,则需要将其切换为指针struct
类型。另外,sqlmock.Rows
用于实现driver.Rows 接口,该接口对于模拟不是必需的或有用的,因此已被删除。希望它不会引起问题。
API
Variables
1
2
3
4
5
6
7
|
var CSVColumnParser = func(s string) []byte {
switch {
case strings.ToLower(s) == "null":
return nil
}
return []byte(s)
}
|
CSVColumnParser是将修饰后的csv列字符串转换为[] byte表示形式的函数。当前将NULL转换为nil
1
|
var ErrCancelled = errors.New("canceling query due to user request")
|
ErrCancelled定义了一个错误值,在这种取消错误的情况下可以预期。
func MonitorPingsOption
1
|
func MonitorPingsOption(monitorPings bool) func(*sqlmock) error
|
MonitorPingsOption确定是否应观察和模拟对驱动程序上Ping的调用。
如果通过true,我们将检查这些调用是否正常。可以使用模拟中的ExpectPing()方法来注册期望。
如果传递了false或忽略了此选项,则在确定期望时将不考虑对Ping的调用,而对ExpectPing的调用则无效。
func NewErrorResult
1
|
func NewErrorResult(err error) driver.Result
|
NewErrorResult创建一个新的sql驱动程序Result,该结果返回两个接口方法都给出的错误
例
Code:
1
2
3
4
5
6
|
db, mock, _ := New()
result := NewErrorResult(fmt.Errorf("some error"))
mock.ExpectExec("^INSERT (.+)").WillReturnResult(result)
res, _ := db.Exec("INSERT something")
_, err := res.LastInsertId()
fmt.Println(err)
|
Output:
func NewResult
1
|
func NewResult(lastInsertID int64, rowsAffected int64) driver.Result
|
NewResult为基于Exec的查询模拟创建新的SQL驱动程序Result。
例:
Code:
1
2
3
4
|
var lastInsertID, affected int64
result := NewResult(lastInsertID, affected)
mock.ExpectExec("^INSERT (.+)").WillReturnResult(result)
fmt.Println(mock.ExpectationsWereMet())
|
Output:
1
2
3
4
5
6
|
there is a remaining expectation which was not matched: ExpectedExec => expecting Exec or ExecContext which:
- matches sql: '^INSERT (.+)'
- is without arguments
- should return Result having:
LastInsertId: 0
RowsAffected: 0
|
func QueryMatcherOption
1
|
func QueryMatcherOption(queryMatcher QueryMatcher) func(*sqlmock) error
|
QueryMatcherOption允许自定义SQL查询匹配器,并以更复杂的方式匹配SQL查询字符串。默认的QueryMatcher是QueryMatcherRegexp。
func ValueConverterOption
1
|
func ValueConverterOption(converter driver.ValueConverter) func(*sqlmock) error
|
ValueConverterOption允许使用自定义ValueConverter创建sqlmock连接,以支持具有特殊数据类型的驱动程序。
type Argument
1
2
3
|
type Argument interface {
Match(driver.Value) bool
}
|
当与ExpectedQuery和ExpectedExec期望一起使用时,Argument接口允许以特定方式匹配任何参数。
func AnyArg
AnyArg将返回一个可以匹配任何类型参数的Argument。
对时间有用。时间或类似类型的参数。
type ExpectedBegin
1
2
3
|
type ExpectedBegin struct {
// contains filtered or unexported fields
}
|
ExpectedBegin用于管理* Sqlmock.ExpectBegin返回的* sql.DB.Begin期望。
func (*ExpectedBegin) String
1
|
func (e *ExpectedBegin) String() string
|
字符串返回字符串表示形式
func(* ExpectedBegin)WillDelayFor
1
|
func (e *ExpectedBegin) WillDelayFor(duration time.Duration) *ExpectedBegin
|
WillDelayFor允许指定延迟结果的持续时间。可以与context一起使用
func(* ExpectedBegin)WillReturnError
1
|
func (e *ExpectedBegin) WillReturnError(err error) *ExpectedBegin
|
WillReturnError允许为* sql.DB.Begin操作设置错误
type ExpectedClose
1
2
3
|
type ExpectedClose struct {
// contains filtered or unexported fields
}
|
ExpectedClose用于管理* Sqlmock.ExpectClose返回的* sql.DB.Close期望。
func (*ExpectedClose) String
1
|
func (e *ExpectedClose) String() string
|
字符串返回字符串表示形式
func(* ExpectedClose)WillReturnError
1
|
func (e *ExpectedClose) WillReturnError(err error) *ExpectedClose
|
WillReturnError允许为* sql.DB.Close操作设置错误
type ExpectedCommit
1
2
3
|
type ExpectedCommit struct {
// contains filtered or unexported fields
}
|
ExpectedCommit用于管理* Sqlmock.ExpectCommit返回的* sql.Tx.Commit期望。
func (*ExpectedCommit) String
1
|
func (e *ExpectedCommit) String() string
|
字符串返回字符串表示形式
func (*ExpectedCommit) WillReturnError
1
|
func (e *ExpectedCommit) WillReturnError(err error) *ExpectedCommit
|
WillReturnError允许为* sql.Tx.Close操作设置错误
type ExpectedExec
1
2
3
|
type ExpectedExec struct {
// contains filtered or unexported fields
}
|
ExpectedExec用于管理* sql.DB.Exec,* sql.Tx.Exec或* sql.Stmt.Exec期望。由* Sqlmock.ExpectExec返回。
例
Code:
1
2
3
4
5
6
|
db, mock, _ := New()
result := NewErrorResult(fmt.Errorf("some error"))
mock.ExpectExec("^INSERT (.+)").WillReturnResult(result)
res, _ := db.Exec("INSERT something")
_, err := res.LastInsertId()
fmt.Println(err)
|
Output:
some error
func (*ExpectedExec) String
1
|
func (e *ExpectedExec) String() string
|
字符串返回字符串表示形式
func(* ExpectedExec)WillDelayFor
1
|
func (e *ExpectedExec) WillDelayFor(duration time.Duration) *ExpectedExec
|
WillDelayFor允许指定延迟结果的持续时间。可以与context一起使用
func (*ExpectedExec) WillReturnError
1
|
func (e *ExpectedExec) WillReturnError(err error) *ExpectedExec
|
WillReturnError允许为预期的数据库执行动作设置错误
func(* ExpectedExec)WillReturnResult
1
|
func (e *ExpectedExec) WillReturnResult(result driver.Result) *ExpectedExec
|
WillReturnResult安排预期的Exec()返回特定结果,存在sqlmock.NewResult(lastInsertID int64,受影响的行int64)方法来构建相应的结果。或者,如果需要针对错误sqlmock.NewErrorResult(err error)测试操作以返回给定的错误。
func(* ExpectedExec)WithArgs
1
|
func (e *ExpectedExec) WithArgs(args ...driver.Value) *ExpectedExec
|
WithArgs将匹配给定的预期args与实际的数据库exec操作参数。如果至少一个参数不匹配,则将返回错误。对于特定的参数,可以使用sqlmock.Argument接口来匹配参数。
type ExpectedPing
1
2
3
|
type ExpectedPing struct {
// contains filtered or unexported fields
}
|
ExpectedPing用于管理* sql.DB.Ping期望。由* Sqlmock.ExpectPing返回。
func (*ExpectedPing) String
1
|
func (e *ExpectedPing) String() string
|
字符串返回字符串表示形式
func (*ExpectedPing) WillDelayFor
1
|
func (e *ExpectedPing) WillDelayFor(duration time.Duration) *ExpectedPing
|
WillDelayFor允许指定延迟结果的持续时间。可以与context一起使用。
func(* ExpectedPing)WillReturnError
1
|
func(e * ExpectedPing)WillReturnError(err error)* ExpectedPing
|
WillReturnError允许为预期的数据库ping设置错误
type ExpectedPrepare
1
2
3
|
type ExpectedPrepare struct {
// contains filtered or unexported fields
}
|
ExpectedPrepare用于管理* sql.DB.Prepare或* sql.Tx.Prepare期望。由* Sqlmock.ExpectPrepare返回。
func(* ExpectedPrepare)ExpectExec
1
|
func(e * ExpectedPrepare)ExpectExec()* ExpectedExec
|
ExpectExec允许在此准备好的语句上使用Exec()。为了避免重复的sql查询字符串匹配,此方法很方便。
func(* ExpectedPrepare)ExpectQuery
1
|
func(e * ExpectedPrepare)ExpectQuery()* ExpectedQuery
|
ExpectQuery允许在此准备好的语句上使用Query()或QueryRow()。为了避免重复的sql查询字符串匹配,此方法很方便。
func (*ExpectedPrepare) String
1
|
func (e *ExpectedPrepare) String() string
|
字符串返回字符串表示形式
func(* ExpectedPrepare)WillBeClosed
1
|
func(e * ExpectedPrepare)WillBeClosed()* ExpectedPrepare
|
WillBeClosed期望此准备好的语句将被关闭。
func(* ExpectedPrepare)WillDelayFor
1
|
func (e *ExpectedPrepare) WillDelayFor(duration time.Duration) *ExpectedPrepare
|
WillDelayFor允许指定延迟结果的持续时间。可以与context一起使用
func(* ExpectedPrepare)WillReturnCloseError
func(e * ExpectedPrepare)WillReturnCloseError(错误错误)* ExpectedPrepare
WillReturnCloseError允许为此准备好的语句设置错误Close操作
func(* ExpectedPrepare)WillReturnError
1
|
func (e *ExpectedPrepare) WillReturnError(err error) *ExpectedPrepare
|
WillReturnError允许为预期的* sql.DB.Prepare或* sql.Tx.Prepare操作设置错误。
type ExpectedQuery
1
2
3
|
type ExpectedQuery struct {
// contains filtered or unexported fields
}
|
ExpectedQuery用于管理* sql.DB.Query,* dql.DB.QueryRow,* sql.Tx.Query,* sql.Tx.QueryRow,* sql.Stmt.Query或* sql.Stmt.QueryRow期望。由* Sqlmock.ExpectQuery返回。
func(* ExpectedQuery)RowsWillBeClosed
1
|
func(e * ExpectedQuery)RowsWillBeClosed()* ExpectedQuery
|
RowsWillBeClosed期望此查询行被关闭。
func (*ExpectedQuery) String
1
|
func (e *ExpectedQuery) String() string
|
字符串返回字符串表示形式
func(* ExpectedQuery)WillDelayFor
1
|
func (e *ExpectedQuery) WillDelayFor(duration time.Duration) *ExpectedQuery
|
WillDelayFor允许指定延迟结果的持续时间。可以与context一起使用
func(* ExpectedQuery)WillReturnError
1
|
func (e *ExpectedQuery) WillReturnError(err error) *ExpectedQuery
|
WillReturnError允许为预期的数据库查询设置错误
func(* ExpectedQuery)WillReturnRows
1
|
func(e * ExpectedQuery)WillReturnRows(rows ... * Rows)* ExpectedQuery
|
WillReturnRows指定将由触发的查询返回的结果行集
func(* ExpectedQuery)WithArgs
1
|
func (e *ExpectedQuery) WithArgs(args ...driver.Value) *ExpectedQuery
|
WithArgs将匹配给定的预期args与实际的数据库查询参数。如果至少一个参数不匹配,则将返回错误。对于特定的参数,可以使用sqlmock.Argument接口来匹配参数。
type ExpectedRollback
1
2
3
|
type ExpectedRollback struct {
// contains filtered or unexported fields
}
|
ExpectedRollback用于管理* Sqlmock.ExpectRollback返回的* sql.Tx.Rollback期望。
func (*ExpectedRollback) String
1
|
func (e *ExpectedRollback) String() string
|
字符串返回字符串表示形式
func(* ExpectedRollback)WillReturnError
1
|
func (e *ExpectedRollback) WillReturnError(err error) *ExpectedRollback
|
WillReturnError允许为* sql.Tx.Rollback操作设置错误
type QueryMatcher
1
2
3
4
5
6
|
type QueryMatcher interface {
// Match expected SQL query string without whitespace to
// actual SQL.
Match(expectedSQL, actualSQL string) error
}
|
QueryMatcher是一个SQL查询字符串匹配器接口,可用于自定义SQL查询字符串的验证。例如,外部库可用于构建和验证所选的SQL ast列。
可以自定义sqlmock以实现通过调用sqlmock.New或sqlmock.NewWithDSN时通过选项配置的其他QueryMatcher,默认QueryMatcher为QueryMatcherRegexp。
1
2
3
4
5
6
7
8
|
var QueryMatcherEqual QueryMatcher = QueryMatcherFunc(func(expectedSQL, actualSQL string) error {
expect := stripQuery(expectedSQL)
actual := stripQuery(actualSQL)
if actual != expect {
return fmt.Errorf(`actual sql: "%s" does not equal to expected "%s"`, actual, expect)
}
return nil
})
|
QueryMatcherEqual是SQL查询匹配器,它仅尝试对预期和实际的SQL字符串进行区分大小写的匹配,而无需空格。
1
2
3
4
5
6
7
8
9
10
11
12
|
var QueryMatcherRegexp QueryMatcher = QueryMatcherFunc(func(expectedSQL, actualSQL string) error {
expect := stripQuery(expectedSQL)
actual := stripQuery(actualSQL)
re, err := regexp.Compile(expect)
if err != nil {
return err
}
if !re.MatchString(actual) {
return fmt.Errorf(`could not match actual sql: "%s" with expected regexp "%s"`, actual, re.String())
}
return nil
})
|
QueryMatcherRegexp是sqlmock使用的默认SQL查询匹配器。它将ExpectedSQL解析为一个正则表达式,并尝试匹配ActualSQL。
例
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// configure to use case sensitive SQL query matcher
// instead of default regular expression matcher
db, mock, err := New(QueryMatcherOption(QueryMatcherEqual))
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).
AddRow(1, "one").
AddRow(2, "two")
mock.ExpectQuery("SELECT * FROM users").WillReturnRows(rows)
rs, err := db.Query("SELECT * FROM users")
if err != nil {
fmt.Println("failed to match expected query")
return
}
defer rs.Close()
for rs.Next() {
var id int
var title string
rs.Scan(&id, &title)
fmt.Println("scanned id:", id, "and title:", title)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
|
Output:
1
2
|
scanned id: 1 and title: one
scanned id: 2 and title: two
|
type QueryMatcherFunc
1
|
type QueryMatcherFunc func(expectedSQL, actualSQL string) error
|
QueryMatcherFunc类型是一个适配器,允许将普通功能用作QueryMatcher。如果f是具有适当签名的函数,则QueryMatcherFunc(f)是调用f的QueryMatcher。
unc (QueryMatcherFunc) Match
1
|
func (f QueryMatcherFunc) Match(expectedSQL, actualSQL string) error
|
Match实现QueryMatcher
type Rows
1
2
3
|
type Rows struct {
// contains filtered or unexported fields
}
|
行是要返回查询结果的行的模拟集合
例
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).
AddRow(1, "one").
AddRow(2, "two")
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var title string
rs.Scan(&id, &title)
fmt.Println("scanned id:", id, "and title:", title)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
|
Output:
1
2
|
scanned id: 1 and title: one
scanned id: 2 and title: two
|
Example (CloseError)
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).CloseError(fmt.Errorf("close error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
// Note: that close will return error only before rows EOF
// that is a default sql package behavior. If you run rs.Next()
// it will handle the error internally and return nil bellow
if err := rs.Close(); err != nil {
fmt.Println("got error:", err)
}
|
Output:
Example (CustomDriverValue)
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "null_int"}).
AddRow(1, 7).
AddRow(5, sql.NullInt64{Int64: 5, Valid: true}).
AddRow(2, sql.NullInt64{})
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var num sql.NullInt64
rs.Scan(&id, &num)
fmt.Println("scanned id:", id, "and null int64:", num)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
|
Output:
1
2
3
|
scanned id: 1 and null int64: {7 true}
scanned id: 5 and null int64: {5 true}
scanned id: 2 and null int64: {0 false}
|
Example (ExpectToBeClosed)
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).AddRow(1, "john")
mock.ExpectQuery("SELECT").WillReturnRows(rows).RowsWillBeClosed()
db.Query("SELECT")
if err := mock.ExpectationsWereMet(); err != nil {
fmt.Println("got error:", err)
}
|
Output:
1
2
3
4
5
|
got error: expected query rows to be closed, but it was not: ExpectedQuery => expecting Query, QueryContext or QueryRow which:
- matches sql: 'SELECT'
- is without arguments
- should return rows:
row 0 - [1 john]
|
Example (RawBytes)
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "binary"}).
AddRow(1, []byte(`one binary value with some text!`)).
AddRow(2, []byte(`two binary value with even more text than the first one`))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
type scanned struct {
id int
raw sql.RawBytes
}
fmt.Println("initial read...")
var ss []scanned
for rs.Next() {
var s scanned
rs.Scan(&s.id, &s.raw)
ss = append(ss, s)
fmt.Println("scanned id:", s.id, "and raw:", string(s.raw))
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
fmt.Println("after reading all...")
for _, s := range ss {
fmt.Println("scanned id:", s.id, "and raw:", string(s.raw))
}
|
Output:
1
2
3
4
5
6
|
initial read...
scanned id: 1 and raw: one binary value with some text!
scanned id: 2 and raw: two binary value with even more text than the first one
after reading all...
scanned id: 1 and raw: ☠☠☠ MEMORY OVERWRITTEN ☠
scanned id: 2 and raw: ☠☠☠ MEMORY OVERWRITTEN ☠☠☠ ☠☠☠ MEMORY
|
Example (RowError)
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
rows := NewRows([]string{"id", "title"}).
AddRow(0, "one").
AddRow(1, "two").
RowError(1, fmt.Errorf("row error"))
mock.ExpectQuery("SELECT").WillReturnRows(rows)
rs, _ := db.Query("SELECT")
defer rs.Close()
for rs.Next() {
var id int
var title string
rs.Scan(&id, &title)
fmt.Println("scanned id:", id, "and title:", title)
}
if rs.Err() != nil {
fmt.Println("got rows error:", rs.Err())
}
|
Output:
1
2
|
scanned id: 0 and title: one
got rows error: row error
|
func NewRows
1
|
func NewRows(columns []string) *Rows
|
NewRows允许从sql driver.Value切片或CSV字符串创建行,并用作sql driver.Rows。如果使用自定义转换器,请改用Sqlmock.NewRows
func(* Rows)AddRow
1
|
func (r *Rows) AddRow(values ...driver.Value) *Rows
|
由数据库driver.Value切片组成的AddRow返回相同的实例以执行后续操作。请注意,值的数量必须与列数匹配
func (*Rows) CloseError
1
|
func (r *Rows) CloseError(err error) *Rows
|
CloseError允许设置将由rows.Close函数返回的错误。
仅当还没有达到rows.Next()EOF时才触发关闭错误,这是默认的sql库行为
func (*Rows) FromCSVString
1
|
func (r *Rows) FromCSVString(s string) *Rows
|
FromCSVString从csv字符串构建行。返回相同的实例以执行后续操作。请注意,值的数量必须与列数匹配
func(* Rows)RowError
1
|
func (r *Rows) RowError(row int, err error) *Rows
|
RowError允许设置一个错误,当读取给定的行号时将返回此错误
type Sqlmock
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
type Sqlmock interface {
// ExpectClose queues an expectation for this database
// action to be triggered. the *ExpectedClose allows
// to mock database response
ExpectClose() *ExpectedClose
// ExpectationsWereMet checks whether all queued expectations
// were met in order. If any of them was not met - an error is returned.
ExpectationsWereMet() error
// ExpectPrepare expects Prepare() to be called with expectedSQL query.
// the *ExpectedPrepare allows to mock database response.
// Note that you may expect Query() or Exec() on the *ExpectedPrepare
// statement to prevent repeating expectedSQL
ExpectPrepare(expectedSQL string) *ExpectedPrepare
// ExpectQuery expects Query() or QueryRow() to be called with expectedSQL query.
// the *ExpectedQuery allows to mock database response.
ExpectQuery(expectedSQL string) *ExpectedQuery
// ExpectExec expects Exec() to be called with expectedSQL query.
// the *ExpectedExec allows to mock database response
ExpectExec(expectedSQL string) *ExpectedExec
// ExpectBegin expects *sql.DB.Begin to be called.
// the *ExpectedBegin allows to mock database response
ExpectBegin() *ExpectedBegin
// ExpectCommit expects *sql.Tx.Commit to be called.
// the *ExpectedCommit allows to mock database response
ExpectCommit() *ExpectedCommit
// ExpectRollback expects *sql.Tx.Rollback to be called.
// the *ExpectedRollback allows to mock database response
ExpectRollback() *ExpectedRollback
// ExpectPing expected *sql.DB.Ping to be called.
// the *ExpectedPing allows to mock database response
//
// Ping support only exists in the SQL library in Go 1.8 and above.
// ExpectPing in Go <=1.7 will return an ExpectedPing but not register
// any expectations.
//
// You must enable pings using MonitorPingsOption for this to register
// any expectations.
ExpectPing() *ExpectedPing
// MatchExpectationsInOrder gives an option whether to match all
// expectations in the order they were set or not.
//
// By default it is set to - true. But if you use goroutines
// to parallelize your query executation, that option may
// be handy.
//
// This option may be turned on anytime during tests. As soon
// as it is switched to false, expectations will be matched
// in any order. Or otherwise if switched to true, any unmatched
// expectations will be expected in order
MatchExpectationsInOrder(bool)
// NewRows allows Rows to be created from a
// sql driver.Value slice or from the CSV string and
// to be used as sql driver.Rows.
NewRows(columns []string) *Rows
}
|
Sqlmock接口用于为任何类型的数据库操作创建期望,以便模拟和测试实际的数据库行为。
示例(Goroutines)
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
db, mock, err := New()
if err != nil {
fmt.Println("failed to open sqlmock database:", err)
}
defer db.Close()
// note this line is important for unordered expectation matching
mock.MatchExpectationsInOrder(false)
result := NewResult(1, 1)
mock.ExpectExec("^UPDATE one").WithArgs("one").WillReturnResult(result)
mock.ExpectExec("^UPDATE two").WithArgs("one", "two").WillReturnResult(result)
mock.ExpectExec("^UPDATE three").WithArgs("one", "two", "three").WillReturnResult(result)
var wg sync.WaitGroup
queries := map[string][]interface{}{
"one": {"one"},
"two": {"one", "two"},
"three": {"one", "two", "three"},
}
wg.Add(len(queries))
for table, args := range queries {
go func(tbl string, a []interface{}) {
if _, err := db.Exec("UPDATE "+tbl, a...); err != nil {
fmt.Println("error was not expected:", err)
}
wg.Done()
}(table, args)
}
wg.Wait()
if err := mock.ExpectationsWereMet(); err != nil {
fmt.Println("there were unfulfilled expectations:", err)
}
|
func New
1
|
func New(options ...func(*sqlmock) error) (*sql.DB, Sqlmock, error)
|
New创建sqlmock数据库连接和一个模拟来管理期望。接受诸如ValueConverterOption之类的选项,以使用特定驱动程序中的ValueConverter。Pings db,以便可以肯定所有期望。
例
1
2
3
4
5
6
7
8
|
db, mock, err := New()
if err != nil {
fmt.Println("expected no error, but got:", err)
return
}
defer db.Close()
// now we can expect operations performed on db
mock.ExpectBegin().WillReturnError(fmt.Errorf("an error will occur on db.Begin() call"))
|
func NewWithDSN
1
|
func NewWithDSN(dsn string, options ...func(*sqlmock) error) (*sql.DB, Sqlmock, error)
|
NewWithDSN创建具有特定DSN的sqlmock数据库连接以及用于管理期望的模拟。接受诸如ValueConverterOption之类的选项,以使用特定驱动程序中的ValueConverter。Pings db,以便可以肯定所有期望。
由于sql抽象库而引入了此方法,该库没有提供使用sql.DB实例进行初始化的方法。例如GORM库。
注意,如果尝试使用已经使用过的dsn进行创建,则会出错
不建议使用此方法,除非您确实需要它并且没有其他方法可以使用。
举例
假设您使用go-mysql-driver,可以尝试一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func recordStats(db *sql.DB, userID, productID int64) (err error) {
tx, err := db.Begin()
if err != nil {
return
}
defer func() {
switch err {
case nil:
err = tx.Commit()
default:
tx.Rollback()
}
}()
if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil {
return
}
if _, err = tx.Exec("INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)", userID, productID); err != nil {
return
}
return
}
func main() {
// @NOTE: the real connection is not required for tests
db, err := sql.Open("mysql", "root@/blog")
if err != nil {
panic(err)
}
defer db.Close()
if err = recordStats(db, 1 /*some user id*/, 5 /*some product id*/); err != nil {
panic(err)
}
}
|
用sqlmock测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
package main
import (
"fmt"
"testing"
"github.com/DATA-DOG/go-sqlmock"
)
// a successful case
func TestShouldUpdateStats(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectBegin()
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
// now we execute our method
if err = recordStats(db, 2, 3); err != nil {
t.Errorf("error was not expected while updating stats: %s", err)
}
// we make sure that all expectations were met
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
// a failing test case
func TestShouldRollbackStatUpdatesOnFailure(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectBegin()
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("INSERT INTO product_viewers").
WithArgs(2, 3).
WillReturnError(fmt.Errorf("some error"))
mock.ExpectRollback()
// now we execute our method
if err = recordStats(db, 2, 3); err == nil {
t.Errorf("was expecting an error, but there was none")
}
// we make sure that all expectations were met
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
|
自定义SQL查询匹配
用户提出了许多有关SQL查询字符串验证或不同匹配选项的请求。现在QueryMatcher,我们已经实现了接口,可以在调用sqlmock.New
或sqlmock.NewWithDSN
时通过选项传递该接口 。
现在,这允许包括一些库,该库将允许例如解析和验证mysqlSQL AST。并创建一个自定义QueryMatcher以便以复杂的方式验证SQL。
默认情况下,sqlmock保留向后兼容性,默认查询匹配器sqlmock.QueryMatcherRegexp
使用期望的SQL字符串作为正则表达式来匹配传入的查询字符串。有一个相等匹配器: QueryMatcherEqual
它将进行完全区分大小写的匹配。
为了自定义QueryMatcher,请使用以下命令:
1
|
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
|
可以根据用户需求完全定制查询匹配器。sqlmock将不提供标准的sql解析匹配器,因为各种驱动程序可能不遵循相同的SQL标准。
示例解析如下图:

看看实际情况中需要注意的点:
-
初始化sqlmock后,需要将sqlmock的db实例赋值给实际调用的数据库,如下图所示:

稍微仔细思考下也能够理解,官网给的示例初始化sqlmock后都没有指定要mock哪个db,显然会抛异常。
-
如何mock返回错误?
使用WillReturnError方法构造返回错误,如下图所示:

匹配参数如time.Time
可能存在某些struct类型的参数,无法通过值轻易地将其进行比较time.Time。在这种情况下, sqlmock提供了一个Argument接口,可以在更复杂的匹配中使用它。这是时间参数匹配的一个简单示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
type AnyTime struct{}
// Match satisfies sqlmock.Argument interface
func (a AnyTime) Match(v driver.Value) bool {
_, ok := v.(time.Time)
return ok
}
func TestAnyTimeArgument(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectExec("INSERT INTO users").
WithArgs("john", AnyTime{}).
WillReturnResult(NewResult(1, 1))
_, err = db.Exec("INSERT INTO users(name, created_at) VALUES (?, ?)", "john", time.Now())
if err != nil {
t.Errorf("error '%s' was not expected, while inserting a row", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
|
它仅断言该参数是time.Time类型。
gomonkey与sqlmock
gomonkey的ApplyMethod方法能不能替代sqlmock?不能!
如下图:被测方法为detailDb.Query(),如果使用gomonkey进行mock,则需要重新query方法:

Query方法的入参和出参如下图:出参包含Rows参数

再来看看Rows结构体,会发现里面的结构十分复杂,根本无法手工构造想要的数据。

综上,在示例特定场景下,无法使用gomonkey来替代sqlmock
场景覆盖
sqlmock是否能覆盖所有sql场景?
目前发现开发底层都使用"github.com/go-sql-driver/mysql"数据库,都能够使用sqlmock库进行mock
参考:
https://kknews.cc/code/jl83xne.html