Skip to content

Commit d11f623

Browse files
committed
implements named arguments support and adds delay expectation for context deadline simulation
1 parent 55ecc5a commit d11f623

File tree

4 files changed

+191
-24
lines changed

4 files changed

+191
-24
lines changed

expectations.go

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"regexp"
88
"strings"
99
"sync"
10+
"time"
1011
)
1112

1213
// an expectation interface
@@ -54,6 +55,7 @@ func (e *ExpectedClose) String() string {
5455
// returned by *Sqlmock.ExpectBegin.
5556
type ExpectedBegin struct {
5657
commonExpectation
58+
delay time.Duration
5759
}
5860

5961
// WillReturnError allows to set an error for *sql.DB.Begin action
@@ -71,6 +73,13 @@ func (e *ExpectedBegin) String() string {
7173
return msg
7274
}
7375

76+
// WillDelayFor allows to specify duration for which it will delay
77+
// result. May be used together with Context
78+
func (e *ExpectedBegin) WillDelayFor(duration time.Duration) *ExpectedBegin {
79+
e.delay = duration
80+
return e
81+
}
82+
7483
// ExpectedCommit is used to manage *sql.Tx.Commit expectation
7584
// returned by *Sqlmock.ExpectCommit.
7685
type ExpectedCommit struct {
@@ -118,7 +127,8 @@ func (e *ExpectedRollback) String() string {
118127
// Returned by *Sqlmock.ExpectQuery.
119128
type ExpectedQuery struct {
120129
queryBasedExpectation
121-
rows driver.Rows
130+
rows driver.Rows
131+
delay time.Duration
122132
}
123133

124134
// WithArgs will match given expected args to actual database query arguments.
@@ -142,6 +152,13 @@ func (e *ExpectedQuery) WillReturnRows(rows driver.Rows) *ExpectedQuery {
142152
return e
143153
}
144154

155+
// WillDelayFor allows to specify duration for which it will delay
156+
// result. May be used together with Context
157+
func (e *ExpectedQuery) WillDelayFor(duration time.Duration) *ExpectedQuery {
158+
e.delay = duration
159+
return e
160+
}
161+
145162
// String returns string representation
146163
func (e *ExpectedQuery) String() string {
147164
msg := "ExpectedQuery => expecting Query or QueryRow which:"
@@ -178,6 +195,7 @@ func (e *ExpectedQuery) String() string {
178195
type ExpectedExec struct {
179196
queryBasedExpectation
180197
result driver.Result
198+
delay time.Duration
181199
}
182200

183201
// WithArgs will match given expected args to actual database exec operation arguments.
@@ -194,6 +212,13 @@ func (e *ExpectedExec) WillReturnError(err error) *ExpectedExec {
194212
return e
195213
}
196214

215+
// WillDelayFor allows to specify duration for which it will delay
216+
// result. May be used together with Context
217+
func (e *ExpectedExec) WillDelayFor(duration time.Duration) *ExpectedExec {
218+
e.delay = duration
219+
return e
220+
}
221+
197222
// String returns string representation
198223
func (e *ExpectedExec) String() string {
199224
msg := "ExpectedExec => expecting Exec which:"
@@ -244,6 +269,7 @@ type ExpectedPrepare struct {
244269
sqlRegex *regexp.Regexp
245270
statement driver.Stmt
246271
closeErr error
272+
delay time.Duration
247273
}
248274

249275
// WillReturnError allows to set an error for the expected *sql.DB.Prepare or *sql.Tx.Prepare action.
@@ -258,6 +284,13 @@ func (e *ExpectedPrepare) WillReturnCloseError(err error) *ExpectedPrepare {
258284
return e
259285
}
260286

287+
// WillDelayFor allows to specify duration for which it will delay
288+
// result. May be used together with Context
289+
func (e *ExpectedPrepare) WillDelayFor(duration time.Duration) *ExpectedPrepare {
290+
e.delay = duration
291+
return e
292+
}
293+
261294
// ExpectQuery allows to expect Query() or QueryRow() on this prepared statement.
262295
// this method is convenient in order to prevent duplicating sql query string matching.
263296
func (e *ExpectedPrepare) ExpectQuery() *ExpectedQuery {
@@ -300,7 +333,7 @@ type queryBasedExpectation struct {
300333
args []driver.Value
301334
}
302335

303-
func (e *queryBasedExpectation) attemptMatch(sql string, args []driver.Value) (err error) {
336+
func (e *queryBasedExpectation) attemptMatch(sql string, args []namedValue) (err error) {
304337
if !e.queryMatches(sql) {
305338
return fmt.Errorf(`could not match sql: "%s" with expected regexp "%s"`, sql, e.sqlRegex.String())
306339
}
@@ -323,7 +356,7 @@ func (e *queryBasedExpectation) queryMatches(sql string) bool {
323356
return e.sqlRegex.MatchString(sql)
324357
}
325358

326-
func (e *queryBasedExpectation) argsMatches(args []driver.Value) error {
359+
func (e *queryBasedExpectation) argsMatches(args []namedValue) error {
327360
if nil == e.args {
328361
return nil
329362
}
@@ -334,14 +367,26 @@ func (e *queryBasedExpectation) argsMatches(args []driver.Value) error {
334367
// custom argument matcher
335368
matcher, ok := e.args[k].(Argument)
336369
if ok {
337-
if !matcher.Match(v) {
370+
// @TODO: does it make sense to pass value instead of named value?
371+
if !matcher.Match(v.Value) {
338372
return fmt.Errorf("matcher %T could not match %d argument %T - %+v", matcher, k, args[k], args[k])
339373
}
340374
continue
341375
}
342376

377+
dval := e.args[k]
378+
if named, isNamed := dval.(namedValue); isNamed {
379+
dval = named.Value
380+
if v.Name != named.Name {
381+
return fmt.Errorf("named argument %d: name: \"%s\" does not match expected: \"%s\"", k, v.Name, named.Name)
382+
}
383+
if v.Ordinal != named.Ordinal {
384+
return fmt.Errorf("named argument %d: ordinal position: \"%d\" does not match expected: \"%d\"", k, v.Ordinal, named.Ordinal)
385+
}
386+
}
387+
343388
// convert to driver converter
344-
darg, err := driver.DefaultParameterConverter.ConvertValue(e.args[k])
389+
darg, err := driver.DefaultParameterConverter.ConvertValue(dval)
345390
if err != nil {
346391
return fmt.Errorf("could not convert %d argument %T - %+v to driver value: %s", k, e.args[k], e.args[k], err)
347392
}
@@ -350,8 +395,8 @@ func (e *queryBasedExpectation) argsMatches(args []driver.Value) error {
350395
return fmt.Errorf("argument %d: non-subset type %T returned from Value", k, darg)
351396
}
352397

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])
398+
if !reflect.DeepEqual(darg, v.Value) {
399+
return fmt.Errorf("argument %d expected [%T - %+v] does not match actual [%T - %+v]", k, darg, darg, v.Value, v.Value)
355400
}
356401
}
357402
return nil

expectations_test.go

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,38 @@ import (
1010

1111
func TestQueryExpectationArgComparison(t *testing.T) {
1212
e := &queryBasedExpectation{}
13-
against := []driver.Value{int64(5)}
13+
against := []namedValue{{Value: int64(5), Ordinal: 1}}
1414
if err := e.argsMatches(against); err != nil {
1515
t.Errorf("arguments should match, since the no expectation was set, but got err: %s", err)
1616
}
1717

1818
e.args = []driver.Value{5, "str"}
1919

20-
against = []driver.Value{int64(5)}
20+
against = []namedValue{{Value: int64(5), Ordinal: 1}}
2121
if err := e.argsMatches(against); err == nil {
2222
t.Error("arguments should not match, since the size is not the same")
2323
}
2424

25-
against = []driver.Value{int64(3), "str"}
25+
against = []namedValue{
26+
{Value: int64(3), Ordinal: 1},
27+
{Value: "str", Ordinal: 2},
28+
}
2629
if err := e.argsMatches(against); err == nil {
2730
t.Error("arguments should not match, since the first argument (int value) is different")
2831
}
2932

30-
against = []driver.Value{int64(5), "st"}
33+
against = []namedValue{
34+
{Value: int64(5), Ordinal: 1},
35+
{Value: "st", Ordinal: 2},
36+
}
3137
if err := e.argsMatches(against); err == nil {
3238
t.Error("arguments should not match, since the second argument (string value) is different")
3339
}
3440

35-
against = []driver.Value{int64(5), "str"}
41+
against = []namedValue{
42+
{Value: int64(5), Ordinal: 1},
43+
{Value: "str", Ordinal: 2},
44+
}
3645
if err := e.argsMatches(against); err != nil {
3746
t.Errorf("arguments should match, but it did not: %s", err)
3847
}
@@ -41,7 +50,10 @@ func TestQueryExpectationArgComparison(t *testing.T) {
4150
tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
4251
e.args = []driver.Value{5, tm}
4352

44-
against = []driver.Value{int64(5), tm}
53+
against = []namedValue{
54+
{Value: int64(5), Ordinal: 1},
55+
{Value: tm, Ordinal: 2},
56+
}
4557
if err := e.argsMatches(against); err != nil {
4658
t.Error("arguments should match, but it did not")
4759
}
@@ -52,29 +64,95 @@ func TestQueryExpectationArgComparison(t *testing.T) {
5264
}
5365
}
5466

67+
func TestQueryExpectationNamedArgComparison(t *testing.T) {
68+
e := &queryBasedExpectation{}
69+
against := []namedValue{{Value: int64(5), Name: "id"}}
70+
if err := e.argsMatches(against); err != nil {
71+
t.Errorf("arguments should match, since the no expectation was set, but got err: %s", err)
72+
}
73+
74+
e.args = []driver.Value{
75+
namedValue{Name: "id", Value: int64(5)},
76+
namedValue{Name: "s", Value: "str"},
77+
}
78+
79+
if err := e.argsMatches(against); err == nil {
80+
t.Error("arguments should not match, since the size is not the same")
81+
}
82+
83+
against = []namedValue{
84+
{Value: int64(5), Name: "id"},
85+
{Value: "str", Name: "s"},
86+
}
87+
88+
if err := e.argsMatches(against); err != nil {
89+
t.Errorf("arguments should have matched, but it did not: %v", err)
90+
}
91+
92+
against = []namedValue{
93+
{Value: int64(5), Name: "id"},
94+
{Value: "str", Name: "username"},
95+
}
96+
97+
if err := e.argsMatches(against); err == nil {
98+
t.Error("arguments matched, but it should have not due to Name")
99+
}
100+
101+
e.args = []driver.Value{
102+
namedValue{Ordinal: 1, Value: int64(5)},
103+
namedValue{Ordinal: 2, Value: "str"},
104+
}
105+
106+
against = []namedValue{
107+
{Value: int64(5), Ordinal: 0},
108+
{Value: "str", Ordinal: 1},
109+
}
110+
111+
if err := e.argsMatches(against); err == nil {
112+
t.Error("arguments matched, but it should have not due to wrong Ordinal position")
113+
}
114+
115+
against = []namedValue{
116+
{Value: int64(5), Ordinal: 1},
117+
{Value: "str", Ordinal: 2},
118+
}
119+
120+
if err := e.argsMatches(against); err != nil {
121+
t.Errorf("arguments should have matched, but it did not: %v", err)
122+
}
123+
}
124+
55125
func TestQueryExpectationArgComparisonBool(t *testing.T) {
56126
var e *queryBasedExpectation
57127

58128
e = &queryBasedExpectation{args: []driver.Value{true}}
59-
against := []driver.Value{true}
129+
against := []namedValue{
130+
{Value: true, Ordinal: 1},
131+
}
60132
if err := e.argsMatches(against); err != nil {
61133
t.Error("arguments should match, since arguments are the same")
62134
}
63135

64136
e = &queryBasedExpectation{args: []driver.Value{false}}
65-
against = []driver.Value{false}
137+
against = []namedValue{
138+
{Value: false, Ordinal: 1},
139+
}
66140
if err := e.argsMatches(against); err != nil {
67141
t.Error("arguments should match, since argument are the same")
68142
}
69143

70144
e = &queryBasedExpectation{args: []driver.Value{true}}
71-
against = []driver.Value{false}
145+
against = []namedValue{
146+
{Value: false, Ordinal: 1},
147+
}
72148
if err := e.argsMatches(against); err == nil {
73149
t.Error("arguments should not match, since argument is different")
74150
}
75151

76152
e = &queryBasedExpectation{args: []driver.Value{false}}
77-
against = []driver.Value{true}
153+
against = []namedValue{
154+
{Value: true, Ordinal: 1},
155+
}
78156
if err := e.argsMatches(against); err == nil {
79157
t.Error("arguments should not match, since argument is different")
80158
}
@@ -117,7 +195,7 @@ func TestBuildQuery(t *testing.T) {
117195
name = 'John'
118196
and
119197
address = 'Jakarta'
120-
198+
121199
`
122200

123201
mock.ExpectQuery(query)

0 commit comments

Comments
 (0)