1
0
mirror of https://github.com/DATA-DOG/go-sqlmock.git synced 2025-03-19 20:57:50 +02:00

more expressive expectation error output

This commit is contained in:
gedi 2015-08-26 16:59:28 +03:00
parent 566ca54083
commit 5c18417f3f
4 changed files with 196 additions and 28 deletions

View File

@ -1,4 +1,5 @@
language: go language: go
sudo: false
go: go:
- 1.2 - 1.2
- 1.3 - 1.3
@ -9,4 +10,3 @@ go:
script: script:
- go test -v ./... - go test -v ./...
- go test -race ./... - go test -race ./...
# linter will follow

View File

@ -2,8 +2,10 @@ package sqlmock
import ( import (
"database/sql/driver" "database/sql/driver"
"fmt"
"reflect" "reflect"
"regexp" "regexp"
"strings"
"sync" "sync"
) )
@ -19,6 +21,7 @@ type expectation interface {
fulfilled() bool fulfilled() bool
Lock() Lock()
Unlock() Unlock()
String() string
} }
// common expectation struct // common expectation struct
@ -45,6 +48,15 @@ func (e *ExpectedClose) WillReturnError(err error) *ExpectedClose {
return e return e
} }
// String returns string representation
func (e *ExpectedClose) String() string {
msg := "ExpectedClose => expecting database Close"
if e.err != nil {
msg += fmt.Sprintf(", which should return error: %s", e.err)
}
return msg
}
// ExpectedBegin is used to manage *sql.DB.Begin expectation // ExpectedBegin is used to manage *sql.DB.Begin expectation
// returned by *Sqlmock.ExpectBegin. // returned by *Sqlmock.ExpectBegin.
type ExpectedBegin struct { type ExpectedBegin struct {
@ -57,6 +69,15 @@ func (e *ExpectedBegin) WillReturnError(err error) *ExpectedBegin {
return e return e
} }
// String returns string representation
func (e *ExpectedBegin) String() string {
msg := "ExpectedBegin => expecting database transaction Begin"
if e.err != nil {
msg += fmt.Sprintf(", which should return error: %s", e.err)
}
return msg
}
// ExpectedCommit is used to manage *sql.Tx.Commit expectation // ExpectedCommit is used to manage *sql.Tx.Commit expectation
// returned by *Sqlmock.ExpectCommit. // returned by *Sqlmock.ExpectCommit.
type ExpectedCommit struct { type ExpectedCommit struct {
@ -69,6 +90,15 @@ func (e *ExpectedCommit) WillReturnError(err error) *ExpectedCommit {
return e return e
} }
// String returns string representation
func (e *ExpectedCommit) String() string {
msg := "ExpectedCommit => expecting transaction Commit"
if e.err != nil {
msg += fmt.Sprintf(", which should return error: %s", e.err)
}
return msg
}
// ExpectedRollback is used to manage *sql.Tx.Rollback expectation // ExpectedRollback is used to manage *sql.Tx.Rollback expectation
// returned by *Sqlmock.ExpectRollback. // returned by *Sqlmock.ExpectRollback.
type ExpectedRollback struct { type ExpectedRollback struct {
@ -81,6 +111,15 @@ func (e *ExpectedRollback) WillReturnError(err error) *ExpectedRollback {
return e return e
} }
// String returns string representation
func (e *ExpectedRollback) String() string {
msg := "ExpectedRollback => expecting transaction Rollback"
if e.err != nil {
msg += fmt.Sprintf(", which should return error: %s", e.err)
}
return msg
}
// ExpectedQuery is used to manage *sql.DB.Query, *dql.DB.QueryRow, *sql.Tx.Query, // ExpectedQuery is used to manage *sql.DB.Query, *dql.DB.QueryRow, *sql.Tx.Query,
// *sql.Tx.QueryRow, *sql.Stmt.Query or *sql.Stmt.QueryRow expectations. // *sql.Tx.QueryRow, *sql.Stmt.Query or *sql.Stmt.QueryRow expectations.
// Returned by *Sqlmock.ExpectQuery. // Returned by *Sqlmock.ExpectQuery.
@ -110,6 +149,37 @@ func (e *ExpectedQuery) WillReturnRows(rows driver.Rows) *ExpectedQuery {
return e return e
} }
// String returns string representation
func (e *ExpectedQuery) String() string {
msg := "ExpectedQuery => expecting Query or QueryRow which:"
msg += "\n - matches sql: '" + e.sqlRegex.String() + "'"
if len(e.args) == 0 {
msg += "\n - is without arguments"
} else {
msg += "\n - is with arguments:\n"
for i, arg := range e.args {
msg += fmt.Sprintf(" %d - %+v\n", i, arg)
}
msg = strings.TrimSpace(msg)
}
if e.rows != nil {
msg += "\n - should return rows:\n"
rs, _ := e.rows.(*rows)
for i, row := range rs.rows {
msg += fmt.Sprintf(" %d - %+v\n", i, row)
}
msg = strings.TrimSpace(msg)
}
if e.err != nil {
msg += fmt.Sprintf("\n - should return error: %s", e.err)
}
return msg
}
// ExpectedExec is used to manage *sql.DB.Exec, *sql.Tx.Exec or *sql.Stmt.Exec expectations. // ExpectedExec is used to manage *sql.DB.Exec, *sql.Tx.Exec or *sql.Stmt.Exec expectations.
// Returned by *Sqlmock.ExpectExec. // Returned by *Sqlmock.ExpectExec.
type ExpectedExec struct { type ExpectedExec struct {
@ -131,6 +201,39 @@ func (e *ExpectedExec) WillReturnError(err error) *ExpectedExec {
return e return e
} }
// String returns string representation
func (e *ExpectedExec) String() string {
msg := "ExpectedExec => expecting Exec which:"
msg += "\n - matches sql: '" + e.sqlRegex.String() + "'"
if len(e.args) == 0 {
msg += "\n - is without arguments"
} else {
msg += "\n - is with arguments:\n"
var margs []string
for i, arg := range e.args {
margs = append(margs, fmt.Sprintf(" %d - %+v", i, arg))
}
msg += strings.Join(margs, "\n")
}
if e.result != nil {
res, _ := e.result.(*result)
msg += "\n - should return Result having:"
msg += fmt.Sprintf("\n LastInsertId: %d", res.insertID)
msg += fmt.Sprintf("\n RowsAffected: %d", res.rowsAffected)
if res.err != nil {
msg += fmt.Sprintf("\n Error: %s", res.err)
}
}
if e.err != nil {
msg += fmt.Sprintf("\n - should return error: %s", e.err)
}
return msg
}
// WillReturnResult arranges for an expected Exec() to return a particular // WillReturnResult arranges for an expected Exec() to return a particular
// result, there is sqlmock.NewResult(lastInsertID int64, affectedRows int64) method // result, there is sqlmock.NewResult(lastInsertID int64, affectedRows int64) method
// to build a corresponding result. Or if actions needs to be tested against errors // to build a corresponding result. Or if actions needs to be tested against errors
@ -180,6 +283,22 @@ func (e *ExpectedPrepare) ExpectExec() *ExpectedExec {
return eq return eq
} }
// String returns string representation
func (e *ExpectedPrepare) String() string {
msg := "ExpectedPrepare => expecting Prepare statement which:"
msg += "\n - matches sql: '" + e.sqlRegex.String() + "'"
if e.err != nil {
msg += fmt.Sprintf("\n - should return error: %s", e.err)
}
if e.closeErr != nil {
msg += fmt.Sprintf("\n - should return error on Close: %s", e.closeErr)
}
return msg
}
// query based expectation // query based expectation
// adds a query matching logic // adds a query matching logic
type queryBasedExpectation struct { type queryBasedExpectation struct {

View File

@ -23,7 +23,12 @@ func ExampleNewResult() {
result := NewResult(lastInsertID, affected) result := NewResult(lastInsertID, affected)
mock.ExpectExec("^INSERT (.+)").WillReturnResult(result) mock.ExpectExec("^INSERT (.+)").WillReturnResult(result)
fmt.Println(mock.ExpectationsWereMet()) fmt.Println(mock.ExpectationsWereMet())
// Output: there is a remaining expectation *sqlmock.ExpectedExec which was not matched yet // Output: there is a remaining expectation which was not matched: ExpectedExec => expecting Exec which:
// - matches sql: '^INSERT (.+)'
// - is without arguments
// - should return Result having:
// LastInsertId: 0
// RowsAffected: 0
} }
func TestShouldReturnValidSqlDriverResult(t *testing.T) { func TestShouldReturnValidSqlDriverResult(t *testing.T) {

View File

@ -1,12 +1,12 @@
/* /*
Package sqlmock provides sql driver mock connecection, which allows to test database, Package sqlmock provides sql driver connection, which allows to test database
create expectations and ensure the correct execution flow of any database operations. interactions by expected calls and simulate their results or errors.
It hooks into Go standard library's database/sql package.
The package provides convenient methods to mock database queries, transactions and It does not require any modifications to your source code in order to test
expect the right execution flow, compare query arguments or even return error instead and mock database operations.
to simulate failures. See the example bellow, which illustrates how convenient it is
to work with. The driver allows to mock any sql driver method behavior. Concurrent actions
are also supported.
*/ */
package sqlmock package sqlmock
@ -61,11 +61,13 @@ func (c *Sqlmock) Close() error {
} }
var expected *ExpectedClose var expected *ExpectedClose
var fulfilled int
var ok bool var ok bool
for _, next := range c.expected { for _, next := range c.expected {
next.Lock() next.Lock()
if next.fulfilled() { if next.fulfilled() {
next.Unlock() next.Unlock()
fulfilled++
continue continue
} }
@ -75,11 +77,16 @@ func (c *Sqlmock) Close() error {
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.MatchExpectationsInOrder {
return fmt.Errorf("call to database Close, was not expected, next expectation is %T as %+v", next, next) return fmt.Errorf("call to database Close, was not expected, next expectation is: %s", next)
} }
} }
if expected == nil { if expected == nil {
return fmt.Errorf("all expectations were already fulfilled, call to database Close was not expected") msg := "call to database Close was not expected"
if fulfilled == len(c.expected) {
msg = "all expectations were already fulfilled, " + msg
}
return fmt.Errorf(msg)
} }
expected.triggered = true expected.triggered = true
@ -92,7 +99,7 @@ func (c *Sqlmock) Close() error {
func (c *Sqlmock) ExpectationsWereMet() error { func (c *Sqlmock) ExpectationsWereMet() error {
for _, e := range c.expected { for _, e := range c.expected {
if !e.fulfilled() { if !e.fulfilled() {
return fmt.Errorf("there is a remaining expectation %T which was not matched yet", e) return fmt.Errorf("there is a remaining expectation which was not matched: %s", e)
} }
} }
return nil return nil
@ -102,10 +109,12 @@ func (c *Sqlmock) ExpectationsWereMet() error {
func (c *Sqlmock) Begin() (driver.Tx, error) { func (c *Sqlmock) Begin() (driver.Tx, error) {
var expected *ExpectedBegin var expected *ExpectedBegin
var ok bool var ok bool
var fulfilled int
for _, next := range c.expected { for _, next := range c.expected {
next.Lock() next.Lock()
if next.fulfilled() { if next.fulfilled() {
next.Unlock() next.Unlock()
fulfilled++
continue continue
} }
@ -115,11 +124,15 @@ func (c *Sqlmock) Begin() (driver.Tx, error) {
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.MatchExpectationsInOrder {
return nil, fmt.Errorf("call to begin transaction, was not expected, next expectation is %T as %+v", next, next) return nil, fmt.Errorf("call to database transaction Begin, was not expected, next expectation is: %s", next)
} }
} }
if expected == nil { if expected == nil {
return nil, fmt.Errorf("all expectations were already fulfilled, call to begin transaction was not expected") msg := "call to database transaction Begin was not expected"
if fulfilled == len(c.expected) {
msg = "all expectations were already fulfilled, " + msg
}
return nil, fmt.Errorf(msg)
} }
expected.triggered = true expected.triggered = true
@ -139,11 +152,13 @@ func (c *Sqlmock) ExpectBegin() *ExpectedBegin {
func (c *Sqlmock) Exec(query string, args []driver.Value) (res driver.Result, err error) { func (c *Sqlmock) Exec(query string, args []driver.Value) (res driver.Result, err error) {
query = stripQuery(query) query = stripQuery(query)
var expected *ExpectedExec var expected *ExpectedExec
var fulfilled int
var ok bool var ok bool
for _, next := range c.expected { for _, next := range c.expected {
next.Lock() next.Lock()
if next.fulfilled() { if next.fulfilled() {
next.Unlock() next.Unlock()
fulfilled++
continue continue
} }
@ -152,7 +167,7 @@ func (c *Sqlmock) Exec(query string, args []driver.Value) (res driver.Result, er
break break
} }
next.Unlock() next.Unlock()
return nil, fmt.Errorf("call to exec query '%s' with args %+v, was not expected, next expectation is %T as %+v", query, args, next, next) return nil, fmt.Errorf("call to exec query '%s' with args %+v, was not expected, next expectation is: %s", query, args, next)
} }
if exec, ok := next.(*ExpectedExec); ok { if exec, ok := next.(*ExpectedExec); ok {
if exec.attemptMatch(query, args) { if exec.attemptMatch(query, args) {
@ -163,7 +178,11 @@ func (c *Sqlmock) Exec(query string, args []driver.Value) (res driver.Result, er
next.Unlock() next.Unlock()
} }
if expected == nil { if expected == nil {
return nil, fmt.Errorf("all expectations were already fulfilled, call to exec '%s' query with args %+v was not expected", query, args) msg := "call to exec '%s' query with args %+v was not expected"
if fulfilled == len(c.expected) {
msg = "all expectations were already fulfilled, " + msg
}
return nil, fmt.Errorf(msg, query, args)
} }
defer expected.Unlock() defer expected.Unlock()
@ -172,8 +191,8 @@ func (c *Sqlmock) Exec(query string, args []driver.Value) (res driver.Result, er
defer func(errp *error, exp *ExpectedExec, q string, a []driver.Value) { defer func(errp *error, exp *ExpectedExec, q string, a []driver.Value) {
if e := recover(); e != nil { if e := recover(); e != nil {
if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion
msg := "exec query \"%s\", args \"%+v\" failed to match expected arguments \"%+v\", reason %s" msg := "exec query \"%s\", args \"%+v\" failed to match with error \"%s\" expectation: %s"
*errp = fmt.Errorf(msg, q, a, exp.args, se) *errp = fmt.Errorf(msg, q, a, se, exp)
} else { } else {
panic(e) // overwise if unknown error panic panic(e) // overwise if unknown error panic
} }
@ -211,11 +230,13 @@ func (c *Sqlmock) ExpectExec(sqlRegexStr string) *ExpectedExec {
// Prepare meets http://golang.org/pkg/database/sql/driver/#Conn interface // Prepare meets http://golang.org/pkg/database/sql/driver/#Conn interface
func (c *Sqlmock) Prepare(query string) (driver.Stmt, error) { func (c *Sqlmock) Prepare(query string) (driver.Stmt, error) {
var expected *ExpectedPrepare var expected *ExpectedPrepare
var fulfilled int
var ok bool var ok bool
for _, next := range c.expected { for _, next := range c.expected {
next.Lock() next.Lock()
if next.fulfilled() { if next.fulfilled() {
next.Unlock() next.Unlock()
fulfilled++
continue continue
} }
@ -225,13 +246,17 @@ func (c *Sqlmock) Prepare(query string) (driver.Stmt, error) {
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.MatchExpectationsInOrder {
return nil, fmt.Errorf("call to Prepare stetement with query '%s', was not expected, next expectation is %T as %+v", query, next, next) return nil, fmt.Errorf("call to Prepare stetement with query '%s', was not expected, next expectation is: %s", query, next)
} }
} }
query = stripQuery(query) query = stripQuery(query)
if expected == nil { if expected == nil {
return nil, fmt.Errorf("all expectations were already fulfilled, call to Prepare '%s' query was not expected", query) msg := "call to Prepare '%s' query was not expected"
if fulfilled == len(c.expected) {
msg = "all expectations were already fulfilled, " + msg
}
return nil, fmt.Errorf(msg, query)
} }
expected.triggered = true expected.triggered = true
@ -254,11 +279,13 @@ func (c *Sqlmock) ExpectPrepare(sqlRegexStr string) *ExpectedPrepare {
func (c *Sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err error) { func (c *Sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err error) {
query = stripQuery(query) query = stripQuery(query)
var expected *ExpectedQuery var expected *ExpectedQuery
var fulfilled int
var ok bool var ok bool
for _, next := range c.expected { for _, next := range c.expected {
next.Lock() next.Lock()
if next.fulfilled() { if next.fulfilled() {
next.Unlock() next.Unlock()
fulfilled++
continue continue
} }
@ -267,7 +294,7 @@ func (c *Sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err
break break
} }
next.Unlock() next.Unlock()
return nil, fmt.Errorf("call to query '%s' with args %+v, was not expected, next expectation is %T as %+v", query, args, next, next) return nil, fmt.Errorf("call to query '%s' with args %+v, was not expected, next expectation is: %s", query, args, next)
} }
if qr, ok := next.(*ExpectedQuery); ok { if qr, ok := next.(*ExpectedQuery); ok {
if qr.attemptMatch(query, args) { if qr.attemptMatch(query, args) {
@ -277,8 +304,13 @@ func (c *Sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err
} }
next.Unlock() next.Unlock()
} }
if expected == nil { if expected == nil {
return nil, fmt.Errorf("all expectations were already fulfilled, call to query '%s' with args %+v was not expected", query, args) msg := "call to query '%s' with args %+v was not expected"
if fulfilled == len(c.expected) {
msg = "all expectations were already fulfilled, " + msg
}
return nil, fmt.Errorf(msg, query, args)
} }
defer expected.Unlock() defer expected.Unlock()
@ -287,8 +319,8 @@ func (c *Sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err
defer func(errp *error, exp *ExpectedQuery, q string, a []driver.Value) { defer func(errp *error, exp *ExpectedQuery, q string, a []driver.Value) {
if e := recover(); e != nil { if e := recover(); e != nil {
if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion
msg := "query \"%s\", args \"%+v\" failed to match expected arguments \"%+v\", reason %s" msg := "query \"%s\", args \"%+v\" failed to match with error \"%s\" expectation: %s"
*errp = fmt.Errorf(msg, q, a, exp.args, se) *errp = fmt.Errorf(msg, q, a, se, exp)
} else { } else {
panic(e) // overwise if unknown error panic panic(e) // overwise if unknown error panic
} }
@ -343,11 +375,13 @@ func (c *Sqlmock) ExpectRollback() *ExpectedRollback {
// Commit meets http://golang.org/pkg/database/sql/driver/#Tx // Commit meets http://golang.org/pkg/database/sql/driver/#Tx
func (c *Sqlmock) Commit() error { func (c *Sqlmock) Commit() error {
var expected *ExpectedCommit var expected *ExpectedCommit
var fulfilled int
var ok bool var ok bool
for _, next := range c.expected { for _, next := range c.expected {
next.Lock() next.Lock()
if next.fulfilled() { if next.fulfilled() {
next.Unlock() next.Unlock()
fulfilled++
continue continue
} }
@ -357,11 +391,15 @@ func (c *Sqlmock) Commit() error {
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.MatchExpectationsInOrder {
return fmt.Errorf("call to commit transaction, was not expected, next expectation is %T as %+v", next, next) return fmt.Errorf("call to commit transaction, was not expected, next expectation is: %s", next)
} }
} }
if expected == nil { if expected == nil {
return fmt.Errorf("all expectations were already fulfilled, call to commit transaction was not expected") msg := "call to commit transaction was not expected"
if fulfilled == len(c.expected) {
msg = "all expectations were already fulfilled, " + msg
}
return fmt.Errorf(msg)
} }
expected.triggered = true expected.triggered = true
@ -372,11 +410,13 @@ func (c *Sqlmock) Commit() error {
// Rollback meets http://golang.org/pkg/database/sql/driver/#Tx // Rollback meets http://golang.org/pkg/database/sql/driver/#Tx
func (c *Sqlmock) Rollback() error { func (c *Sqlmock) Rollback() error {
var expected *ExpectedRollback var expected *ExpectedRollback
var fulfilled int
var ok bool var ok bool
for _, next := range c.expected { for _, next := range c.expected {
next.Lock() next.Lock()
if next.fulfilled() { if next.fulfilled() {
next.Unlock() next.Unlock()
fulfilled++
continue continue
} }
@ -386,11 +426,15 @@ func (c *Sqlmock) Rollback() error {
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.MatchExpectationsInOrder {
return fmt.Errorf("call to rollback transaction, was not expected, next expectation is %T as %+v", next, next) return fmt.Errorf("call to rollback transaction, was not expected, next expectation is: %s", next)
} }
} }
if expected == nil { if expected == nil {
return fmt.Errorf("all expectations were already fulfilled, call to rollback transaction was not expected") msg := "call to rollback transaction was not expected"
if fulfilled == len(c.expected) {
msg = "all expectations were already fulfilled, " + msg
}
return fmt.Errorf(msg)
} }
expected.triggered = true expected.triggered = true