简介

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:

1
some error

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

1
func AnyArg() Argument

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
funce * ExpectedPingWillReturnErrorerr 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
funce * ExpectedPrepareExpectExec()* ExpectedExec

ExpectExec允许在此准备好的语句上使用Exec()。为了避免重复的sql查询字符串匹配,此方法很方便。

func(* ExpectedPrepare)ExpectQuery

1
funce * ExpectedPrepareExpectQuery()* ExpectedQuery

ExpectQuery允许在此准备好的语句上使用Query()或QueryRow()。为了避免重复的sql查询字符串匹配,此方法很方便。

func (*ExpectedPrepare) String

1
func (e *ExpectedPrepare) String() string

字符串返回字符串表示形式

func(* ExpectedPrepare)WillBeClosed

1
funce * ExpectedPrepareWillBeClosed()* 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
funce * ExpectedQueryRowsWillBeClosed()* 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
funce * ExpectedQueryWillReturnRowsrows ... * 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:

1
got error: close error

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.Newsqlmock.NewWithDSN时通过选项传递该接口 。

现在,这允许包括一些库,该库将允许例如解析和验证mysqlSQL AST。并创建一个自定义QueryMatcher以便以复杂的方式验证SQL。

默认情况下,sqlmock保留向后兼容性,默认查询匹配器sqlmock.QueryMatcherRegexp 使用期望的SQL字符串作为正则表达式来匹配传入的查询字符串。有一个相等匹配器: QueryMatcherEqual它将进行完全区分大小写的匹配。

为了自定义QueryMatcher,请使用以下命令:

1
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))

可以根据用户需求完全定制查询匹配器。sqlmock将不提供标准的sql解析匹配器,因为各种驱动程序可能不遵循相同的SQL标准。

示例解析如下图:

看看实际情况中需要注意的点:

  1. 初始化sqlmock后,需要将sqlmock的db实例赋值给实际调用的数据库,如下图所示:

    稍微仔细思考下也能够理解,官网给的示例初始化sqlmock后都没有指定要mock哪个db,显然会抛异常。

  2. 如何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