Skip to content

Commit b983233

Browse files
authored
Merge pull request #68 from DATA-DOG/go1.8
Support for go 1.8 SQL features
2 parents 55ecc5a + 372a183 commit b983233

14 files changed

+1030
-125
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ go:
77
- 1.5
88
- 1.6
99
- 1.7
10+
- 1.8
1011
- tip
1112

1213
script: go test -race -coverprofile=coverage.txt -covermode=atomic

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The three clause BSD license (http://en.wikipedia.org/wiki/BSD_licenses)
22

3-
Copyright (c) 2013-2016, DATA-DOG team
3+
Copyright (c) 2013-2017, DATA-DOG team
44
All rights reserved.
55

66
Redistribution and use in source and binary forms, with or without

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,16 @@ maintain correct **TDD** workflow.
1010

1111
- this library is now complete and stable. (you may not find new changes for this reason)
1212
- supports concurrency and multiple connections.
13+
- supports **go1.8** Context related feature mocking and Named sql parameters.
1314
- does not require any modifications to your source code.
1415
- the driver allows to mock any sql driver method behavior.
1516
- has strict by default expectation order matching.
16-
- has no vendor dependencies.
17+
- has no third party dependencies.
1718

1819
## Install
1920

2021
go get gopkg.in/DATA-DOG/go-sqlmock.v1
2122

22-
If you need an old version, checkout **go-sqlmock** at gopkg.in:
23-
24-
go get gopkg.in/DATA-DOG/go-sqlmock.v0
25-
2623
## Documentation and Examples
2724

2825
Visit [godoc](http://godoc.org/github.com/DATA-DOG/go-sqlmock) for general examples and public api reference.
@@ -187,8 +184,11 @@ It only asserts that argument is of `time.Time` type.
187184

188185
go test -race
189186

190-
## Changes
187+
## Change Log
191188

189+
- **2017-02-09** - implemented support for **go1.8** features. **Rows** interface was changed to struct
190+
but contains all methods as before and should maintain backwards compatibility. **ExpectedQuery.WillReturnRows** may now
191+
accept multiple row sets.
192192
- **2016-11-02** - `db.Prepare()` was not validating expected prepare SQL
193193
query. It should still be validated even if Exec or Query is not
194194
executed on that prepared statement.

expectations.go

Lines changed: 33 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ package sqlmock
33
import (
44
"database/sql/driver"
55
"fmt"
6-
"reflect"
76
"regexp"
87
"strings"
98
"sync"
9+
"time"
1010
)
1111

1212
// an expectation interface
@@ -54,6 +54,7 @@ func (e *ExpectedClose) String() string {
5454
// returned by *Sqlmock.ExpectBegin.
5555
type ExpectedBegin struct {
5656
commonExpectation
57+
delay time.Duration
5758
}
5859

5960
// WillReturnError allows to set an error for *sql.DB.Begin action
@@ -71,6 +72,13 @@ func (e *ExpectedBegin) String() string {
7172
return msg
7273
}
7374

75+
// WillDelayFor allows to specify duration for which it will delay
76+
// result. May be used together with Context
77+
func (e *ExpectedBegin) WillDelayFor(duration time.Duration) *ExpectedBegin {
78+
e.delay = duration
79+
return e
80+
}
81+
7482
// ExpectedCommit is used to manage *sql.Tx.Commit expectation
7583
// returned by *Sqlmock.ExpectCommit.
7684
type ExpectedCommit struct {
@@ -118,7 +126,8 @@ func (e *ExpectedRollback) String() string {
118126
// Returned by *Sqlmock.ExpectQuery.
119127
type ExpectedQuery struct {
120128
queryBasedExpectation
121-
rows driver.Rows
129+
rows driver.Rows
130+
delay time.Duration
122131
}
123132

124133
// WithArgs will match given expected args to actual database query arguments.
@@ -135,10 +144,10 @@ func (e *ExpectedQuery) WillReturnError(err error) *ExpectedQuery {
135144
return e
136145
}
137146

138-
// WillReturnRows specifies the set of resulting rows that will be returned
139-
// by the triggered query
140-
func (e *ExpectedQuery) WillReturnRows(rows driver.Rows) *ExpectedQuery {
141-
e.rows = rows
147+
// WillDelayFor allows to specify duration for which it will delay
148+
// result. May be used together with Context
149+
func (e *ExpectedQuery) WillDelayFor(duration time.Duration) *ExpectedQuery {
150+
e.delay = duration
142151
return e
143152
}
144153

@@ -158,12 +167,7 @@ func (e *ExpectedQuery) String() string {
158167
}
159168

160169
if e.rows != nil {
161-
msg += "\n - should return rows:\n"
162-
rs, _ := e.rows.(*rows)
163-
for i, row := range rs.rows {
164-
msg += fmt.Sprintf(" %d - %+v\n", i, row)
165-
}
166-
msg = strings.TrimSpace(msg)
170+
msg += fmt.Sprintf("\n - %s", e.rows)
167171
}
168172

169173
if e.err != nil {
@@ -178,6 +182,7 @@ func (e *ExpectedQuery) String() string {
178182
type ExpectedExec struct {
179183
queryBasedExpectation
180184
result driver.Result
185+
delay time.Duration
181186
}
182187

183188
// WithArgs will match given expected args to actual database exec operation arguments.
@@ -194,6 +199,13 @@ func (e *ExpectedExec) WillReturnError(err error) *ExpectedExec {
194199
return e
195200
}
196201

202+
// WillDelayFor allows to specify duration for which it will delay
203+
// result. May be used together with Context
204+
func (e *ExpectedExec) WillDelayFor(duration time.Duration) *ExpectedExec {
205+
e.delay = duration
206+
return e
207+
}
208+
197209
// String returns string representation
198210
func (e *ExpectedExec) String() string {
199211
msg := "ExpectedExec => expecting Exec which:"
@@ -244,6 +256,7 @@ type ExpectedPrepare struct {
244256
sqlRegex *regexp.Regexp
245257
statement driver.Stmt
246258
closeErr error
259+
delay time.Duration
247260
}
248261

249262
// WillReturnError allows to set an error for the expected *sql.DB.Prepare or *sql.Tx.Prepare action.
@@ -258,6 +271,13 @@ func (e *ExpectedPrepare) WillReturnCloseError(err error) *ExpectedPrepare {
258271
return e
259272
}
260273

274+
// WillDelayFor allows to specify duration for which it will delay
275+
// result. May be used together with Context
276+
func (e *ExpectedPrepare) WillDelayFor(duration time.Duration) *ExpectedPrepare {
277+
e.delay = duration
278+
return e
279+
}
280+
261281
// ExpectQuery allows to expect Query() or QueryRow() on this prepared statement.
262282
// this method is convenient in order to prevent duplicating sql query string matching.
263283
func (e *ExpectedPrepare) ExpectQuery() *ExpectedQuery {
@@ -300,7 +320,7 @@ type queryBasedExpectation struct {
300320
args []driver.Value
301321
}
302322

303-
func (e *queryBasedExpectation) attemptMatch(sql string, args []driver.Value) (err error) {
323+
func (e *queryBasedExpectation) attemptMatch(sql string, args []namedValue) (err error) {
304324
if !e.queryMatches(sql) {
305325
return fmt.Errorf(`could not match sql: "%s" with expected regexp "%s"`, sql, e.sqlRegex.String())
306326
}
@@ -322,37 +342,3 @@ func (e *queryBasedExpectation) attemptMatch(sql string, args []driver.Value) (e
322342
func (e *queryBasedExpectation) queryMatches(sql string) bool {
323343
return e.sqlRegex.MatchString(sql)
324344
}
325-
326-
func (e *queryBasedExpectation) argsMatches(args []driver.Value) error {
327-
if nil == e.args {
328-
return nil
329-
}
330-
if len(args) != len(e.args) {
331-
return fmt.Errorf("expected %d, but got %d arguments", len(e.args), len(args))
332-
}
333-
for k, v := range args {
334-
// custom argument matcher
335-
matcher, ok := e.args[k].(Argument)
336-
if ok {
337-
if !matcher.Match(v) {
338-
return fmt.Errorf("matcher %T could not match %d argument %T - %+v", matcher, k, args[k], args[k])
339-
}
340-
continue
341-
}
342-
343-
// convert to driver converter
344-
darg, err := driver.DefaultParameterConverter.ConvertValue(e.args[k])
345-
if err != nil {
346-
return fmt.Errorf("could not convert %d argument %T - %+v to driver value: %s", k, e.args[k], e.args[k], err)
347-
}
348-
349-
if !driver.IsValue(darg) {
350-
return fmt.Errorf("argument %d: non-subset type %T returned from Value", k, darg)
351-
}
352-
353-
if !reflect.DeepEqual(darg, args[k]) {
354-
return fmt.Errorf("argument %d expected [%T - %+v] does not match actual [%T - %+v]", k, darg, darg, args[k], args[k])
355-
}
356-
}
357-
return nil
358-
}

expectations_before_go18.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// +build !go1.8
2+
3+
package sqlmock
4+
5+
import (
6+
"database/sql/driver"
7+
"fmt"
8+
"reflect"
9+
)
10+
11+
// WillReturnRows specifies the set of resulting rows that will be returned
12+
// by the triggered query
13+
func (e *ExpectedQuery) WillReturnRows(rows *Rows) *ExpectedQuery {
14+
e.rows = &rowSets{sets: []*Rows{rows}}
15+
return e
16+
}
17+
18+
func (e *queryBasedExpectation) argsMatches(args []namedValue) error {
19+
if nil == e.args {
20+
return nil
21+
}
22+
if len(args) != len(e.args) {
23+
return fmt.Errorf("expected %d, but got %d arguments", len(e.args), len(args))
24+
}
25+
for k, v := range args {
26+
// custom argument matcher
27+
matcher, ok := e.args[k].(Argument)
28+
if ok {
29+
// @TODO: does it make sense to pass value instead of named value?
30+
if !matcher.Match(v.Value) {
31+
return fmt.Errorf("matcher %T could not match %d argument %T - %+v", matcher, k, args[k], args[k])
32+
}
33+
continue
34+
}
35+
36+
dval := e.args[k]
37+
// convert to driver converter
38+
darg, err := driver.DefaultParameterConverter.ConvertValue(dval)
39+
if err != nil {
40+
return fmt.Errorf("could not convert %d argument %T - %+v to driver value: %s", k, e.args[k], e.args[k], err)
41+
}
42+
43+
if !driver.IsValue(darg) {
44+
return fmt.Errorf("argument %d: non-subset type %T returned from Value", k, darg)
45+
}
46+
47+
if !reflect.DeepEqual(darg, v.Value) {
48+
return fmt.Errorf("argument %d expected [%T - %+v] does not match actual [%T - %+v]", k, darg, darg, v.Value, v.Value)
49+
}
50+
}
51+
return nil
52+
}

expectations_go18.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// +build go1.8
2+
3+
package sqlmock
4+
5+
import (
6+
"database/sql"
7+
"database/sql/driver"
8+
"fmt"
9+
"reflect"
10+
)
11+
12+
// WillReturnRows specifies the set of resulting rows that will be returned
13+
// by the triggered query
14+
func (e *ExpectedQuery) WillReturnRows(rows ...*Rows) *ExpectedQuery {
15+
sets := make([]*Rows, len(rows))
16+
for i, r := range rows {
17+
sets[i] = r
18+
}
19+
e.rows = &rowSets{sets: sets}
20+
return e
21+
}
22+
23+
func (e *queryBasedExpectation) argsMatches(args []namedValue) error {
24+
if nil == e.args {
25+
return nil
26+
}
27+
if len(args) != len(e.args) {
28+
return fmt.Errorf("expected %d, but got %d arguments", len(e.args), len(args))
29+
}
30+
// @TODO should we assert either all args are named or ordinal?
31+
for k, v := range args {
32+
// custom argument matcher
33+
matcher, ok := e.args[k].(Argument)
34+
if ok {
35+
if !matcher.Match(v.Value) {
36+
return fmt.Errorf("matcher %T could not match %d argument %T - %+v", matcher, k, args[k], args[k])
37+
}
38+
continue
39+
}
40+
41+
dval := e.args[k]
42+
if named, isNamed := dval.(sql.NamedArg); isNamed {
43+
dval = named.Value
44+
if v.Name != named.Name {
45+
return fmt.Errorf("named argument %d: name: \"%s\" does not match expected: \"%s\"", k, v.Name, named.Name)
46+
}
47+
} else if k+1 != v.Ordinal {
48+
return fmt.Errorf("argument %d: ordinal position: %d does not match expected: %d", k, k+1, v.Ordinal)
49+
}
50+
51+
// convert to driver converter
52+
darg, err := driver.DefaultParameterConverter.ConvertValue(dval)
53+
if err != nil {
54+
return fmt.Errorf("could not convert %d argument %T - %+v to driver value: %s", k, e.args[k], e.args[k], err)
55+
}
56+
57+
if !driver.IsValue(darg) {
58+
return fmt.Errorf("argument %d: non-subset type %T returned from Value", k, darg)
59+
}
60+
61+
if !reflect.DeepEqual(darg, v.Value) {
62+
return fmt.Errorf("argument %d expected [%T - %+v] does not match actual [%T - %+v]", k, darg, darg, v.Value, v.Value)
63+
}
64+
}
65+
return nil
66+
}

expectations_go18_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// +build go1.8
2+
3+
package sqlmock
4+
5+
import (
6+
"database/sql"
7+
"database/sql/driver"
8+
"testing"
9+
)
10+
11+
func TestQueryExpectationNamedArgComparison(t *testing.T) {
12+
e := &queryBasedExpectation{}
13+
against := []namedValue{{Value: int64(5), Name: "id"}}
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)
16+
}
17+
18+
e.args = []driver.Value{
19+
sql.Named("id", 5),
20+
sql.Named("s", "str"),
21+
}
22+
23+
if err := e.argsMatches(against); err == nil {
24+
t.Error("arguments should not match, since the size is not the same")
25+
}
26+
27+
against = []namedValue{
28+
{Value: int64(5), Name: "id"},
29+
{Value: "str", Name: "s"},
30+
}
31+
32+
if err := e.argsMatches(against); err != nil {
33+
t.Errorf("arguments should have matched, but it did not: %v", err)
34+
}
35+
36+
against = []namedValue{
37+
{Value: int64(5), Name: "id"},
38+
{Value: "str", Name: "username"},
39+
}
40+
41+
if err := e.argsMatches(against); err == nil {
42+
t.Error("arguments matched, but it should have not due to Name")
43+
}
44+
45+
e.args = []driver.Value{int64(5), "str"}
46+
47+
against = []namedValue{
48+
{Value: int64(5), Ordinal: 0},
49+
{Value: "str", Ordinal: 1},
50+
}
51+
52+
if err := e.argsMatches(against); err == nil {
53+
t.Error("arguments matched, but it should have not due to wrong Ordinal position")
54+
}
55+
56+
against = []namedValue{
57+
{Value: int64(5), Ordinal: 1},
58+
{Value: "str", Ordinal: 2},
59+
}
60+
61+
if err := e.argsMatches(against); err != nil {
62+
t.Errorf("arguments should have matched, but it did not: %v", err)
63+
}
64+
}

0 commit comments

Comments
 (0)