mirror of
https://github.com/zhashkevych/go-sqlxmock.git
synced 2024-11-24 08:12:13 +02:00
Merge pull request #32 from DATA-DOG/natural-argument-converters
Natural argument converters
This commit is contained in:
commit
b54b0cd4c8
@ -5,7 +5,7 @@ go:
|
|||||||
- 1.3
|
- 1.3
|
||||||
- 1.4
|
- 1.4
|
||||||
- 1.5
|
- 1.5
|
||||||
- release
|
- 1.6
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
script: go test -race
|
script: go test -race
|
||||||
|
@ -188,6 +188,11 @@ It only asserts that argument is of `time.Time` type.
|
|||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
|
|
||||||
|
- **2016-02-23** - added **sqlmock.AnyArg()** function to provide any kind
|
||||||
|
of argument matcher.
|
||||||
|
- **2016-02-23** - convert expected arguments to driver.Value as natural
|
||||||
|
driver does, the change may affect time.Time comparison and will be
|
||||||
|
stricter. See [issue](https://github.com/DATA-DOG/go-sqlmock/issues/31).
|
||||||
- **2015-08-27** - **v1** api change, concurrency support, all known issues fixed.
|
- **2015-08-27** - **v1** api change, concurrency support, all known issues fixed.
|
||||||
- **2014-08-16** instead of **panic** during reflect type mismatch when comparing query arguments - now return error
|
- **2014-08-16** instead of **panic** during reflect type mismatch when comparing query arguments - now return error
|
||||||
- **2014-08-14** added **sqlmock.NewErrorResult** which gives an option to return driver.Result with errors for
|
- **2014-08-14** added **sqlmock.NewErrorResult** which gives an option to return driver.Result with errors for
|
||||||
|
24
argument.go
Normal file
24
argument.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package sqlmock
|
||||||
|
|
||||||
|
import "database/sql/driver"
|
||||||
|
|
||||||
|
// Argument interface allows to match
|
||||||
|
// any argument in specific way when used with
|
||||||
|
// ExpectedQuery and ExpectedExec expectations.
|
||||||
|
type Argument interface {
|
||||||
|
Match(driver.Value) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyArg will return an Argument which can
|
||||||
|
// match any kind of arguments.
|
||||||
|
//
|
||||||
|
// Useful for time.Time or similar kinds of arguments.
|
||||||
|
func AnyArg() Argument {
|
||||||
|
return anyArgument{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type anyArgument struct{}
|
||||||
|
|
||||||
|
func (a anyArgument) Match(_ driver.Value) bool {
|
||||||
|
return true
|
||||||
|
}
|
@ -3,19 +3,11 @@ package sqlmock
|
|||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Argument interface allows to match
|
|
||||||
// any argument in specific way when used with
|
|
||||||
// ExpectedQuery and ExpectedExec expectations.
|
|
||||||
type Argument interface {
|
|
||||||
Match(driver.Value) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// an expectation interface
|
// an expectation interface
|
||||||
type expectation interface {
|
type expectation interface {
|
||||||
fulfilled() bool
|
fulfilled() bool
|
||||||
@ -307,16 +299,22 @@ type queryBasedExpectation struct {
|
|||||||
args []driver.Value
|
args []driver.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *queryBasedExpectation) attemptMatch(sql string, args []driver.Value) (ret bool) {
|
func (e *queryBasedExpectation) attemptMatch(sql string, args []driver.Value) (err error) {
|
||||||
if !e.queryMatches(sql) {
|
if !e.queryMatches(sql) {
|
||||||
return
|
return fmt.Errorf(`could not match sql: "%s" with expected regexp "%s"`, sql, e.sqlRegex.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
defer recover() // ignore panic since we attempt a match
|
// catch panic
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
_, ok := e.(error)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf(e.(string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if e.argsMatches(args) {
|
err = e.argsMatches(args)
|
||||||
return true
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,50 +322,36 @@ func (e *queryBasedExpectation) queryMatches(sql string) bool {
|
|||||||
return e.sqlRegex.MatchString(sql)
|
return e.sqlRegex.MatchString(sql)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *queryBasedExpectation) argsMatches(args []driver.Value) bool {
|
func (e *queryBasedExpectation) argsMatches(args []driver.Value) error {
|
||||||
if nil == e.args {
|
if nil == e.args {
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
if len(args) != len(e.args) {
|
if len(args) != len(e.args) {
|
||||||
return false
|
return fmt.Errorf("expected %d, but got %d arguments", len(e.args), len(args))
|
||||||
}
|
}
|
||||||
for k, v := range args {
|
for k, v := range args {
|
||||||
|
// custom argument matcher
|
||||||
matcher, ok := e.args[k].(Argument)
|
matcher, ok := e.args[k].(Argument)
|
||||||
if ok {
|
if ok {
|
||||||
if !matcher.Match(v) {
|
if !matcher.Match(v) {
|
||||||
return false
|
return fmt.Errorf("matcher %T could not match %d argument %T - %+v", matcher, k, args[k], args[k])
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
vi := reflect.ValueOf(v)
|
|
||||||
ai := reflect.ValueOf(e.args[k])
|
// convert to driver converter
|
||||||
switch vi.Kind() {
|
darg, err := driver.DefaultParameterConverter.ConvertValue(e.args[k])
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
if err != nil {
|
||||||
if vi.Int() != ai.Int() {
|
return fmt.Errorf("could not convert %d argument %T - %+v to driver value: %s", k, e.args[k], e.args[k], err)
|
||||||
return false
|
}
|
||||||
}
|
|
||||||
case reflect.Float32, reflect.Float64:
|
if !driver.IsValue(darg) {
|
||||||
if vi.Float() != ai.Float() {
|
return fmt.Errorf("argument %d: non-subset type %T returned from Value", k, darg)
|
||||||
return false
|
}
|
||||||
}
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
if darg != args[k] {
|
||||||
if vi.Uint() != ai.Uint() {
|
return fmt.Errorf("argument %d expected [%T - %+v] does not match actual [%T - %+v]", k, darg, darg, args[k], args[k])
|
||||||
return false
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
if vi.String() != ai.String() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case reflect.Bool:
|
|
||||||
if vi.Bool() != ai.Bool() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// compare types like time.Time based on type only
|
|
||||||
if vi.Kind() != ai.Kind() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -8,55 +8,47 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type matcher struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m matcher) Match(driver.Value) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueryExpectationArgComparison(t *testing.T) {
|
func TestQueryExpectationArgComparison(t *testing.T) {
|
||||||
e := &queryBasedExpectation{}
|
e := &queryBasedExpectation{}
|
||||||
against := []driver.Value{5}
|
against := []driver.Value{int64(5)}
|
||||||
if !e.argsMatches(against) {
|
if err := e.argsMatches(against); err != nil {
|
||||||
t.Error("arguments should match, since the no expectation was set")
|
t.Errorf("arguments should match, since the no expectation was set, but got err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.args = []driver.Value{5, "str"}
|
e.args = []driver.Value{5, "str"}
|
||||||
|
|
||||||
against = []driver.Value{5}
|
against = []driver.Value{int64(5)}
|
||||||
if e.argsMatches(against) {
|
if err := e.argsMatches(against); err == nil {
|
||||||
t.Error("arguments should not match, since the size is not the same")
|
t.Error("arguments should not match, since the size is not the same")
|
||||||
}
|
}
|
||||||
|
|
||||||
against = []driver.Value{3, "str"}
|
against = []driver.Value{int64(3), "str"}
|
||||||
if e.argsMatches(against) {
|
if err := e.argsMatches(against); err == nil {
|
||||||
t.Error("arguments should not match, since the first argument (int value) is different")
|
t.Error("arguments should not match, since the first argument (int value) is different")
|
||||||
}
|
}
|
||||||
|
|
||||||
against = []driver.Value{5, "st"}
|
against = []driver.Value{int64(5), "st"}
|
||||||
if e.argsMatches(against) {
|
if err := e.argsMatches(against); err == nil {
|
||||||
t.Error("arguments should not match, since the second argument (string value) is different")
|
t.Error("arguments should not match, since the second argument (string value) is different")
|
||||||
}
|
}
|
||||||
|
|
||||||
against = []driver.Value{5, "str"}
|
against = []driver.Value{int64(5), "str"}
|
||||||
if !e.argsMatches(against) {
|
if err := e.argsMatches(against); err != nil {
|
||||||
t.Error("arguments should match, but it did not")
|
t.Errorf("arguments should match, but it did not: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.args = []driver.Value{5, time.Now()}
|
|
||||||
|
|
||||||
const longForm = "Jan 2, 2006 at 3:04pm (MST)"
|
const longForm = "Jan 2, 2006 at 3:04pm (MST)"
|
||||||
tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
|
tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
|
||||||
|
e.args = []driver.Value{5, tm}
|
||||||
|
|
||||||
against = []driver.Value{5, tm}
|
against = []driver.Value{int64(5), tm}
|
||||||
if !e.argsMatches(against) {
|
if err := e.argsMatches(against); err != nil {
|
||||||
t.Error("arguments should match (time will be compared only by type), but it did not")
|
t.Error("arguments should match, but it did not")
|
||||||
}
|
}
|
||||||
|
|
||||||
against = []driver.Value{5, matcher{}}
|
e.args = []driver.Value{5, AnyArg()}
|
||||||
if !e.argsMatches(against) {
|
if err := e.argsMatches(against); err != nil {
|
||||||
t.Error("arguments should match, but it did not")
|
t.Errorf("arguments should match, but it did not: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,25 +57,25 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) {
|
|||||||
|
|
||||||
e = &queryBasedExpectation{args: []driver.Value{true}}
|
e = &queryBasedExpectation{args: []driver.Value{true}}
|
||||||
against := []driver.Value{true}
|
against := []driver.Value{true}
|
||||||
if !e.argsMatches(against) {
|
if err := e.argsMatches(against); err != nil {
|
||||||
t.Error("arguments should match, since arguments are the same")
|
t.Error("arguments should match, since arguments are the same")
|
||||||
}
|
}
|
||||||
|
|
||||||
e = &queryBasedExpectation{args: []driver.Value{false}}
|
e = &queryBasedExpectation{args: []driver.Value{false}}
|
||||||
against = []driver.Value{false}
|
against = []driver.Value{false}
|
||||||
if !e.argsMatches(against) {
|
if err := e.argsMatches(against); err != nil {
|
||||||
t.Error("arguments should match, since argument are the same")
|
t.Error("arguments should match, since argument are the same")
|
||||||
}
|
}
|
||||||
|
|
||||||
e = &queryBasedExpectation{args: []driver.Value{true}}
|
e = &queryBasedExpectation{args: []driver.Value{true}}
|
||||||
against = []driver.Value{false}
|
against = []driver.Value{false}
|
||||||
if e.argsMatches(against) {
|
if err := e.argsMatches(against); err == nil {
|
||||||
t.Error("arguments should not match, since argument is different")
|
t.Error("arguments should not match, since argument is different")
|
||||||
}
|
}
|
||||||
|
|
||||||
e = &queryBasedExpectation{args: []driver.Value{false}}
|
e = &queryBasedExpectation{args: []driver.Value{false}}
|
||||||
against = []driver.Value{true}
|
against = []driver.Value{true}
|
||||||
if e.argsMatches(against) {
|
if err := e.argsMatches(against); err == nil {
|
||||||
t.Error("arguments should not match, since argument is different")
|
t.Error("arguments should not match, since argument is different")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
sqlmock.go
35
sqlmock.go
@ -14,7 +14,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -216,7 +215,7 @@ func (c *sqlmock) Exec(query string, args []driver.Value) (res driver.Result, er
|
|||||||
return nil, fmt.Errorf("call to exec query '%s' with args %+v, was not expected, next expectation is: %s", query, args, 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 err := exec.attemptMatch(query, args); err == nil {
|
||||||
expected = exec
|
expected = exec
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -233,24 +232,13 @@ func (c *sqlmock) Exec(query string, args []driver.Value) (res driver.Result, er
|
|||||||
|
|
||||||
defer expected.Unlock()
|
defer expected.Unlock()
|
||||||
expected.triggered = true
|
expected.triggered = true
|
||||||
// converts panic to error in case of reflect value type mismatch
|
|
||||||
defer func(errp *error, exp *ExpectedExec, q string, a []driver.Value) {
|
|
||||||
if e := recover(); e != nil {
|
|
||||||
if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion
|
|
||||||
msg := "exec query \"%s\", args \"%+v\" failed to match with error \"%s\" expectation: %s"
|
|
||||||
*errp = fmt.Errorf(msg, q, a, se, exp)
|
|
||||||
} else {
|
|
||||||
panic(e) // overwise if unknown error panic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(&err, expected, query, args)
|
|
||||||
|
|
||||||
if !expected.queryMatches(query) {
|
if !expected.queryMatches(query) {
|
||||||
return nil, fmt.Errorf("exec query '%s', does not match regex '%s'", query, expected.sqlRegex.String())
|
return nil, fmt.Errorf("exec query '%s', does not match regex '%s'", query, expected.sqlRegex.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !expected.argsMatches(args) {
|
if err := expected.argsMatches(args); err != nil {
|
||||||
return nil, fmt.Errorf("exec query '%s', args %+v does not match expected %+v", query, args, expected.args)
|
return nil, fmt.Errorf("exec query '%s', arguments do not match: %s", query, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expected.err != nil {
|
if expected.err != nil {
|
||||||
@ -335,7 +323,7 @@ func (c *sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err
|
|||||||
return nil, fmt.Errorf("call to query '%s' with args %+v, was not expected, next expectation is: %s", query, args, 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 err := qr.attemptMatch(query, args); err == nil {
|
||||||
expected = qr
|
expected = qr
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -353,24 +341,13 @@ func (c *sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err
|
|||||||
|
|
||||||
defer expected.Unlock()
|
defer expected.Unlock()
|
||||||
expected.triggered = true
|
expected.triggered = true
|
||||||
// converts panic to error in case of reflect value type mismatch
|
|
||||||
defer func(errp *error, exp *ExpectedQuery, q string, a []driver.Value) {
|
|
||||||
if e := recover(); e != nil {
|
|
||||||
if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion
|
|
||||||
msg := "query \"%s\", args \"%+v\" failed to match with error \"%s\" expectation: %s"
|
|
||||||
*errp = fmt.Errorf(msg, q, a, se, exp)
|
|
||||||
} else {
|
|
||||||
panic(e) // overwise if unknown error panic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(&err, expected, query, args)
|
|
||||||
|
|
||||||
if !expected.queryMatches(query) {
|
if !expected.queryMatches(query) {
|
||||||
return nil, fmt.Errorf("query '%s', does not match regex [%s]", query, expected.sqlRegex.String())
|
return nil, fmt.Errorf("query '%s', does not match regex [%s]", query, expected.sqlRegex.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !expected.argsMatches(args) {
|
if err := expected.argsMatches(args); err != nil {
|
||||||
return nil, fmt.Errorf("query '%s', args %+v does not match expected %+v", query, args, expected.args)
|
return nil, fmt.Errorf("exec query '%s', arguments do not match: %s", query, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if expected.err != nil {
|
if expected.err != nil {
|
||||||
|
33
statement_test.go
Normal file
33
statement_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// +build go1.6
|
||||||
|
|
||||||
|
package sqlmock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExpectedPreparedStatemtCloseError(t *testing.T) {
|
||||||
|
conn, mock, err := New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to open sqlmock database:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mock.ExpectBegin()
|
||||||
|
want := errors.New("STMT ERROR")
|
||||||
|
mock.ExpectPrepare("SELECT").WillReturnCloseError(want)
|
||||||
|
|
||||||
|
txn, err := conn.Begin()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while opening transaction:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := txn.Prepare("SELECT")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error while preparing a statement:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.Close(); err != want {
|
||||||
|
t.Fatalf("Got = %v, want = %v", err, want)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user