Skip to content

Commit b54b0cd

Browse files
committed
Merge pull request #32 from DATA-DOG/natural-argument-converters
Natural argument converters
2 parents 808cdc9 + cee8a78 commit b54b0cd

File tree

7 files changed

+123
-108
lines changed

7 files changed

+123
-108
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go:
55
- 1.3
66
- 1.4
77
- 1.5
8-
- release
8+
- 1.6
99
- tip
1010

1111
script: go test -race

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ It only asserts that argument is of `time.Time` type.
188188

189189
## Changes
190190

191+
- **2016-02-23** - added **sqlmock.AnyArg()** function to provide any kind
192+
of argument matcher.
193+
- **2016-02-23** - convert expected arguments to driver.Value as natural
194+
driver does, the change may affect time.Time comparison and will be
195+
stricter. See [issue](https://github.com/DATA-DOG/go-sqlmock/issues/31).
191196
- **2015-08-27** - **v1** api change, concurrency support, all known issues fixed.
192197
- **2014-08-16** instead of **panic** during reflect type mismatch when comparing query arguments - now return error
193198
- **2014-08-14** added **sqlmock.NewErrorResult** which gives an option to return driver.Result with errors for

argument.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package sqlmock
2+
3+
import "database/sql/driver"
4+
5+
// Argument interface allows to match
6+
// any argument in specific way when used with
7+
// ExpectedQuery and ExpectedExec expectations.
8+
type Argument interface {
9+
Match(driver.Value) bool
10+
}
11+
12+
// AnyArg will return an Argument which can
13+
// match any kind of arguments.
14+
//
15+
// Useful for time.Time or similar kinds of arguments.
16+
func AnyArg() Argument {
17+
return anyArgument{}
18+
}
19+
20+
type anyArgument struct{}
21+
22+
func (a anyArgument) Match(_ driver.Value) bool {
23+
return true
24+
}

expectations.go

Lines changed: 31 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,11 @@ package sqlmock
33
import (
44
"database/sql/driver"
55
"fmt"
6-
"reflect"
76
"regexp"
87
"strings"
98
"sync"
109
)
1110

12-
// Argument interface allows to match
13-
// any argument in specific way when used with
14-
// ExpectedQuery and ExpectedExec expectations.
15-
type Argument interface {
16-
Match(driver.Value) bool
17-
}
18-
1911
// an expectation interface
2012
type expectation interface {
2113
fulfilled() bool
@@ -307,67 +299,59 @@ type queryBasedExpectation struct {
307299
args []driver.Value
308300
}
309301

310-
func (e *queryBasedExpectation) attemptMatch(sql string, args []driver.Value) (ret bool) {
302+
func (e *queryBasedExpectation) attemptMatch(sql string, args []driver.Value) (err error) {
311303
if !e.queryMatches(sql) {
312-
return
304+
return fmt.Errorf(`could not match sql: "%s" with expected regexp "%s"`, sql, e.sqlRegex.String())
313305
}
314306

315-
defer recover() // ignore panic since we attempt a match
307+
// catch panic
308+
defer func() {
309+
if e := recover(); e != nil {
310+
_, ok := e.(error)
311+
if !ok {
312+
err = fmt.Errorf(e.(string))
313+
}
314+
}
315+
}()
316316

317-
if e.argsMatches(args) {
318-
return true
319-
}
317+
err = e.argsMatches(args)
320318
return
321319
}
322320

323321
func (e *queryBasedExpectation) queryMatches(sql string) bool {
324322
return e.sqlRegex.MatchString(sql)
325323
}
326324

327-
func (e *queryBasedExpectation) argsMatches(args []driver.Value) bool {
325+
func (e *queryBasedExpectation) argsMatches(args []driver.Value) error {
328326
if nil == e.args {
329-
return true
327+
return nil
330328
}
331329
if len(args) != len(e.args) {
332-
return false
330+
return fmt.Errorf("expected %d, but got %d arguments", len(e.args), len(args))
333331
}
334332
for k, v := range args {
333+
// custom argument matcher
335334
matcher, ok := e.args[k].(Argument)
336335
if ok {
337336
if !matcher.Match(v) {
338-
return false
337+
return fmt.Errorf("matcher %T could not match %d argument %T - %+v", matcher, k, args[k], args[k])
339338
}
340339
continue
341340
}
342-
vi := reflect.ValueOf(v)
343-
ai := reflect.ValueOf(e.args[k])
344-
switch vi.Kind() {
345-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
346-
if vi.Int() != ai.Int() {
347-
return false
348-
}
349-
case reflect.Float32, reflect.Float64:
350-
if vi.Float() != ai.Float() {
351-
return false
352-
}
353-
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
354-
if vi.Uint() != ai.Uint() {
355-
return false
356-
}
357-
case reflect.String:
358-
if vi.String() != ai.String() {
359-
return false
360-
}
361-
case reflect.Bool:
362-
if vi.Bool() != ai.Bool() {
363-
return false
364-
}
365-
default:
366-
// compare types like time.Time based on type only
367-
if vi.Kind() != ai.Kind() {
368-
return false
369-
}
341+
342+
// convert to driver converter
343+
darg, err := driver.DefaultParameterConverter.ConvertValue(e.args[k])
344+
if err != nil {
345+
return fmt.Errorf("could not convert %d argument %T - %+v to driver value: %s", k, e.args[k], e.args[k], err)
346+
}
347+
348+
if !driver.IsValue(darg) {
349+
return fmt.Errorf("argument %d: non-subset type %T returned from Value", k, darg)
350+
}
351+
352+
if darg != args[k] {
353+
return fmt.Errorf("argument %d expected [%T - %+v] does not match actual [%T - %+v]", k, darg, darg, args[k], args[k])
370354
}
371355
}
372-
return true
356+
return nil
373357
}

expectations_test.go

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,47 @@ import (
88
"time"
99
)
1010

11-
type matcher struct {
12-
}
13-
14-
func (m matcher) Match(driver.Value) bool {
15-
return true
16-
}
17-
1811
func TestQueryExpectationArgComparison(t *testing.T) {
1912
e := &queryBasedExpectation{}
20-
against := []driver.Value{5}
21-
if !e.argsMatches(against) {
22-
t.Error("arguments should match, since the no expectation was set")
13+
against := []driver.Value{int64(5)}
14+
if err := e.argsMatches(against); err != nil {
15+
t.Errorf("arguments should match, since the no expectation was set, but got err: %s", err)
2316
}
2417

2518
e.args = []driver.Value{5, "str"}
2619

27-
against = []driver.Value{5}
28-
if e.argsMatches(against) {
20+
against = []driver.Value{int64(5)}
21+
if err := e.argsMatches(against); err == nil {
2922
t.Error("arguments should not match, since the size is not the same")
3023
}
3124

32-
against = []driver.Value{3, "str"}
33-
if e.argsMatches(against) {
25+
against = []driver.Value{int64(3), "str"}
26+
if err := e.argsMatches(against); err == nil {
3427
t.Error("arguments should not match, since the first argument (int value) is different")
3528
}
3629

37-
against = []driver.Value{5, "st"}
38-
if e.argsMatches(against) {
30+
against = []driver.Value{int64(5), "st"}
31+
if err := e.argsMatches(against); err == nil {
3932
t.Error("arguments should not match, since the second argument (string value) is different")
4033
}
4134

42-
against = []driver.Value{5, "str"}
43-
if !e.argsMatches(against) {
44-
t.Error("arguments should match, but it did not")
35+
against = []driver.Value{int64(5), "str"}
36+
if err := e.argsMatches(against); err != nil {
37+
t.Errorf("arguments should match, but it did not: %s", err)
4538
}
4639

47-
e.args = []driver.Value{5, time.Now()}
48-
4940
const longForm = "Jan 2, 2006 at 3:04pm (MST)"
5041
tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
42+
e.args = []driver.Value{5, tm}
5143

52-
against = []driver.Value{5, tm}
53-
if !e.argsMatches(against) {
54-
t.Error("arguments should match (time will be compared only by type), but it did not")
44+
against = []driver.Value{int64(5), tm}
45+
if err := e.argsMatches(against); err != nil {
46+
t.Error("arguments should match, but it did not")
5547
}
5648

57-
against = []driver.Value{5, matcher{}}
58-
if !e.argsMatches(against) {
59-
t.Error("arguments should match, but it did not")
49+
e.args = []driver.Value{5, AnyArg()}
50+
if err := e.argsMatches(against); err != nil {
51+
t.Errorf("arguments should match, but it did not: %s", err)
6052
}
6153
}
6254

@@ -65,25 +57,25 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) {
6557

6658
e = &queryBasedExpectation{args: []driver.Value{true}}
6759
against := []driver.Value{true}
68-
if !e.argsMatches(against) {
60+
if err := e.argsMatches(against); err != nil {
6961
t.Error("arguments should match, since arguments are the same")
7062
}
7163

7264
e = &queryBasedExpectation{args: []driver.Value{false}}
7365
against = []driver.Value{false}
74-
if !e.argsMatches(against) {
66+
if err := e.argsMatches(against); err != nil {
7567
t.Error("arguments should match, since argument are the same")
7668
}
7769

7870
e = &queryBasedExpectation{args: []driver.Value{true}}
7971
against = []driver.Value{false}
80-
if e.argsMatches(against) {
72+
if err := e.argsMatches(against); err == nil {
8173
t.Error("arguments should not match, since argument is different")
8274
}
8375

8476
e = &queryBasedExpectation{args: []driver.Value{false}}
8577
against = []driver.Value{true}
86-
if e.argsMatches(against) {
78+
if err := e.argsMatches(against); err == nil {
8779
t.Error("arguments should not match, since argument is different")
8880
}
8981
}

sqlmock.go

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"database/sql"
1515
"database/sql/driver"
1616
"fmt"
17-
"reflect"
1817
"regexp"
1918
)
2019

@@ -216,7 +215,7 @@ func (c *sqlmock) Exec(query string, args []driver.Value) (res driver.Result, er
216215
return nil, fmt.Errorf("call to exec query '%s' with args %+v, was not expected, next expectation is: %s", query, args, next)
217216
}
218217
if exec, ok := next.(*ExpectedExec); ok {
219-
if exec.attemptMatch(query, args) {
218+
if err := exec.attemptMatch(query, args); err == nil {
220219
expected = exec
221220
break
222221
}
@@ -233,24 +232,13 @@ func (c *sqlmock) Exec(query string, args []driver.Value) (res driver.Result, er
233232

234233
defer expected.Unlock()
235234
expected.triggered = true
236-
// converts panic to error in case of reflect value type mismatch
237-
defer func(errp *error, exp *ExpectedExec, q string, a []driver.Value) {
238-
if e := recover(); e != nil {
239-
if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion
240-
msg := "exec query \"%s\", args \"%+v\" failed to match with error \"%s\" expectation: %s"
241-
*errp = fmt.Errorf(msg, q, a, se, exp)
242-
} else {
243-
panic(e) // overwise if unknown error panic
244-
}
245-
}
246-
}(&err, expected, query, args)
247235

248236
if !expected.queryMatches(query) {
249237
return nil, fmt.Errorf("exec query '%s', does not match regex '%s'", query, expected.sqlRegex.String())
250238
}
251239

252-
if !expected.argsMatches(args) {
253-
return nil, fmt.Errorf("exec query '%s', args %+v does not match expected %+v", query, args, expected.args)
240+
if err := expected.argsMatches(args); err != nil {
241+
return nil, fmt.Errorf("exec query '%s', arguments do not match: %s", query, err)
254242
}
255243

256244
if expected.err != nil {
@@ -335,7 +323,7 @@ func (c *sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err
335323
return nil, fmt.Errorf("call to query '%s' with args %+v, was not expected, next expectation is: %s", query, args, next)
336324
}
337325
if qr, ok := next.(*ExpectedQuery); ok {
338-
if qr.attemptMatch(query, args) {
326+
if err := qr.attemptMatch(query, args); err == nil {
339327
expected = qr
340328
break
341329
}
@@ -353,24 +341,13 @@ func (c *sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err
353341

354342
defer expected.Unlock()
355343
expected.triggered = true
356-
// converts panic to error in case of reflect value type mismatch
357-
defer func(errp *error, exp *ExpectedQuery, q string, a []driver.Value) {
358-
if e := recover(); e != nil {
359-
if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion
360-
msg := "query \"%s\", args \"%+v\" failed to match with error \"%s\" expectation: %s"
361-
*errp = fmt.Errorf(msg, q, a, se, exp)
362-
} else {
363-
panic(e) // overwise if unknown error panic
364-
}
365-
}
366-
}(&err, expected, query, args)
367344

368345
if !expected.queryMatches(query) {
369346
return nil, fmt.Errorf("query '%s', does not match regex [%s]", query, expected.sqlRegex.String())
370347
}
371348

372-
if !expected.argsMatches(args) {
373-
return nil, fmt.Errorf("query '%s', args %+v does not match expected %+v", query, args, expected.args)
349+
if err := expected.argsMatches(args); err != nil {
350+
return nil, fmt.Errorf("exec query '%s', arguments do not match: %s", query, err)
374351
}
375352

376353
if expected.err != nil {

statement_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// +build go1.6
2+
3+
package sqlmock
4+
5+
import (
6+
"errors"
7+
"testing"
8+
)
9+
10+
func TestExpectedPreparedStatemtCloseError(t *testing.T) {
11+
conn, mock, err := New()
12+
if err != nil {
13+
t.Fatalf("failed to open sqlmock database:", err)
14+
}
15+
16+
mock.ExpectBegin()
17+
want := errors.New("STMT ERROR")
18+
mock.ExpectPrepare("SELECT").WillReturnCloseError(want)
19+
20+
txn, err := conn.Begin()
21+
if err != nil {
22+
t.Fatalf("unexpected error while opening transaction:", err)
23+
}
24+
25+
stmt, err := txn.Prepare("SELECT")
26+
if err != nil {
27+
t.Fatalf("unexpected error while preparing a statement:", err)
28+
}
29+
30+
if err := stmt.Close(); err != want {
31+
t.Fatalf("Got = %v, want = %v", err, want)
32+
}
33+
}

0 commit comments

Comments
 (0)