Skip to content

Commit a6a27b7

Browse files
committed
fix: make no argument passed validation opt-in
1 parent b2d135c commit a6a27b7

8 files changed

+114
-15
lines changed

expectations.go

+33
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,27 @@ type ExpectedQuery struct {
134134
// WithArgs will match given expected args to actual database query arguments.
135135
// if at least one argument does not match, it will return an error. For specific
136136
// arguments an sqlmock.Argument interface can be used to match an argument.
137+
// Must not be used together with WithoutArgs()
137138
func (e *ExpectedQuery) WithArgs(args ...driver.Value) *ExpectedQuery {
139+
if e.noArgs {
140+
panic("WithArgs() and WithoutArgs() must not be used together")
141+
}
138142
e.args = args
139143
return e
140144
}
141145

146+
// WithoutArgs will ensure that no arguments are passed for this query.
147+
// if at least one argument is passed, it will return an error. This allows
148+
// for stricter validation of the query arguments.
149+
// Must no be used together with WithArgs()
150+
func (e *ExpectedQuery) WithoutArgs() *ExpectedQuery {
151+
if len(e.args) > 0 {
152+
panic("WithoutArgs() and WithArgs() must not be used together")
153+
}
154+
e.noArgs = true
155+
return e
156+
}
157+
142158
// RowsWillBeClosed expects this query rows to be closed.
143159
func (e *ExpectedQuery) RowsWillBeClosed() *ExpectedQuery {
144160
e.rowsMustBeClosed = true
@@ -195,11 +211,27 @@ type ExpectedExec struct {
195211
// WithArgs will match given expected args to actual database exec operation arguments.
196212
// if at least one argument does not match, it will return an error. For specific
197213
// arguments an sqlmock.Argument interface can be used to match an argument.
214+
// Must not be used together with WithoutArgs()
198215
func (e *ExpectedExec) WithArgs(args ...driver.Value) *ExpectedExec {
216+
if len(e.args) > 0 {
217+
panic("WithArgs() and WithoutArgs() must not be used together")
218+
}
199219
e.args = args
200220
return e
201221
}
202222

223+
// WithoutArgs will ensure that no args are passed for this expected database exec action.
224+
// if at least one argument is passed, it will return an error. This allows for stricter
225+
// validation of the query arguments.
226+
// Must not be used together with WithArgs()
227+
func (e *ExpectedExec) WithoutArgs() *ExpectedExec {
228+
if len(e.args) > 0 {
229+
panic("WithoutArgs() and WithArgs() must not be used together")
230+
}
231+
e.noArgs = true
232+
return e
233+
}
234+
203235
// WillReturnError allows to set an error for expected database exec action
204236
func (e *ExpectedExec) WillReturnError(err error) *ExpectedExec {
205237
e.err = err
@@ -338,6 +370,7 @@ type queryBasedExpectation struct {
338370
expectSQL string
339371
converter driver.ValueConverter
340372
args []driver.Value
373+
noArgs bool // ensure no args are passed
341374
}
342375

343376
// ExpectedPing is used to manage *sql.DB.Ping expectations.

expectations_before_go18.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build !go1.8
12
// +build !go1.8
23

34
package sqlmock
@@ -17,7 +18,7 @@ func (e *ExpectedQuery) WillReturnRows(rows *Rows) *ExpectedQuery {
1718

1819
func (e *queryBasedExpectation) argsMatches(args []namedValue) error {
1920
if nil == e.args {
20-
if len(args) > 0 {
21+
if e.noArgs && len(args) > 0 {
2122
return fmt.Errorf("expected 0, but got %d arguments", len(args))
2223
}
2324
return nil

expectations_before_go18_test.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build !go1.8
12
// +build !go1.8
23

34
package sqlmock
@@ -9,10 +10,15 @@ import (
910
)
1011

1112
func TestQueryExpectationArgComparison(t *testing.T) {
12-
e := &queryBasedExpectation{converter: driver.DefaultParameterConverter}
13+
e := &queryBasedExpectation{converter: driver.DefaultParameterConverter, noArgs: true}
1314
against := []namedValue{{Value: int64(5), Ordinal: 1}}
1415
if err := e.argsMatches(against); err == nil {
15-
t.Error("arguments should not match, since no expectation was set, but argument was passed")
16+
t.Error("arguments should not match, since argument was passed, but noArgs was set")
17+
}
18+
19+
e.noArgs = false
20+
if err := e.argsMatches(against); err != nil {
21+
t.Error("arguments should match, since argument was passed, but no expected args or noArgs was set")
1622
}
1723

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

expectations_go18.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build go1.8
12
// +build go1.8
23

34
package sqlmock
@@ -30,7 +31,7 @@ func (e *ExpectedQuery) WillReturnRows(rows ...*Rows) *ExpectedQuery {
3031

3132
func (e *queryBasedExpectation) argsMatches(args []driver.NamedValue) error {
3233
if nil == e.args {
33-
if len(args) > 0 {
34+
if e.noArgs && len(args) > 0 {
3435
return fmt.Errorf("expected 0, but got %d arguments", len(args))
3536
}
3637
return nil

expectations_go18_test.go

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build go1.8
12
// +build go1.8
23

34
package sqlmock
@@ -10,10 +11,15 @@ import (
1011
)
1112

1213
func TestQueryExpectationArgComparison(t *testing.T) {
13-
e := &queryBasedExpectation{converter: driver.DefaultParameterConverter}
14+
e := &queryBasedExpectation{converter: driver.DefaultParameterConverter, noArgs: true}
1415
against := []driver.NamedValue{{Value: int64(5), Ordinal: 1}}
1516
if err := e.argsMatches(against); err == nil {
16-
t.Error("arguments should not match, since no expectation was set, but argument was passed")
17+
t.Error("arguments should not match, since argument was passed, but noArgs was set")
18+
}
19+
20+
e.noArgs = false
21+
if err := e.argsMatches(against); err != nil {
22+
t.Error("arguments should match, since argument was passed, but no expected args or noArgs was set")
1723
}
1824

1925
e.args = []driver.Value{5, "str"}
@@ -102,10 +108,15 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) {
102108
}
103109

104110
func TestQueryExpectationNamedArgComparison(t *testing.T) {
105-
e := &queryBasedExpectation{converter: driver.DefaultParameterConverter}
111+
e := &queryBasedExpectation{converter: driver.DefaultParameterConverter, noArgs: true}
106112
against := []driver.NamedValue{{Value: int64(5), Name: "id"}}
107113
if err := e.argsMatches(against); err == nil {
108-
t.Errorf("arguments should not match, since no expectation was set, but argument was passed")
114+
t.Error("arguments should not match, since argument was passed, but noArgs was set")
115+
}
116+
117+
e.noArgs = false
118+
if err := e.argsMatches(against); err != nil {
119+
t.Error("arguments should match, since argument was passed, but no expected args or noArgs was set")
109120
}
110121

111122
e.args = []driver.Value{

expectations_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,25 @@ func TestCustomValueConverterQueryScan(t *testing.T) {
101101
t.Error(err)
102102
}
103103
}
104+
105+
func TestQueryWithNoArgsAndWithArgsPanic(t *testing.T) {
106+
defer func() {
107+
if r := recover(); r != nil {
108+
return
109+
}
110+
t.Error("Expected panic for using WithArgs and ExpectNoArgs together")
111+
}()
112+
mock := &sqlmock{}
113+
mock.ExpectQuery("SELECT (.+) FROM user").WithArgs("John").WithoutArgs()
114+
}
115+
116+
func TestExecWithNoArgsAndWithArgsPanic(t *testing.T) {
117+
defer func() {
118+
if r := recover(); r != nil {
119+
return
120+
}
121+
t.Error("Expected panic for using WithArgs and ExpectNoArgs together")
122+
}()
123+
mock := &sqlmock{}
124+
mock.ExpectExec("^INSERT INTO user").WithArgs("John").WithoutArgs()
125+
}

sqlmock_go18_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build go1.8
12
// +build go1.8
23

34
package sqlmock
@@ -437,7 +438,6 @@ func TestContextExecErrorDelay(t *testing.T) {
437438
// test that return of error is delayed
438439
var delay time.Duration = 100 * time.Millisecond
439440
mock.ExpectExec("^INSERT INTO articles").
440-
WithArgs("hello").
441441
WillReturnError(errors.New("slow fail")).
442442
WillDelayFor(delay)
443443

sqlmock_test.go

+31-6
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,16 @@ func TestRunExecsWithExpectedErrorMeetsExpectations(t *testing.T) {
749749
}
750750
}
751751

752+
func TestRunExecsWithNoArgsExpectedMeetsExpectations(t *testing.T) {
753+
db, dbmock, _ := New()
754+
dbmock.ExpectExec("THE FIRST EXEC").WithoutArgs().WillReturnResult(NewResult(0, 0))
755+
756+
_, err := db.Exec("THE FIRST EXEC", "foobar")
757+
if err == nil {
758+
t.Fatalf("expected error, but there wasn't any")
759+
}
760+
}
761+
752762
func TestRunQueryWithExpectedErrorMeetsExpectations(t *testing.T) {
753763
db, dbmock, _ := New()
754764
dbmock.ExpectQuery("THE FIRST QUERY").WillReturnError(fmt.Errorf("big bad bug"))
@@ -959,7 +969,7 @@ func TestPrepareExec(t *testing.T) {
959969
mock.ExpectBegin()
960970
ep := mock.ExpectPrepare("INSERT INTO ORDERS\\(ID, STATUS\\) VALUES \\(\\?, \\?\\)")
961971
for i := 0; i < 3; i++ {
962-
ep.ExpectExec().WithArgs(i, "Hello"+strconv.Itoa(i)).WillReturnResult(NewResult(1, 1))
972+
ep.ExpectExec().WillReturnResult(NewResult(1, 1))
963973
}
964974
mock.ExpectCommit()
965975
tx, _ := db.Begin()
@@ -1073,7 +1083,7 @@ func TestPreparedStatementCloseExpectation(t *testing.T) {
10731083
defer db.Close()
10741084

10751085
ep := mock.ExpectPrepare("INSERT INTO ORDERS").WillBeClosed()
1076-
ep.ExpectExec().WithArgs(1, "Hello").WillReturnResult(NewResult(1, 1))
1086+
ep.ExpectExec().WillReturnResult(NewResult(1, 1))
10771087

10781088
stmt, err := db.Prepare("INSERT INTO ORDERS(ID, STATUS) VALUES (?, ?)")
10791089
if err != nil {
@@ -1104,7 +1114,6 @@ func TestExecExpectationErrorDelay(t *testing.T) {
11041114
// test that return of error is delayed
11051115
var delay time.Duration = 100 * time.Millisecond
11061116
mock.ExpectExec("^INSERT INTO articles").
1107-
WithArgs("hello").
11081117
WillReturnError(errors.New("slow fail")).
11091118
WillDelayFor(delay)
11101119

@@ -1230,10 +1239,10 @@ func Test_sqlmock_Prepare_and_Exec(t *testing.T) {
12301239

12311240
mock.ExpectPrepare("SELECT (.+) FROM users WHERE (.+)")
12321241
expected := NewResult(1, 1)
1233-
mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)").WithArgs("test").
1242+
mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)").
12341243
WillReturnResult(expected)
12351244
expectedRows := mock.NewRows([]string{"id", "name", "email"}).AddRow(1, "test", "[email protected]")
1236-
mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WithArgs("test").WillReturnRows(expectedRows)
1245+
mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WillReturnRows(expectedRows)
12371246

12381247
got, err := mock.(*sqlmock).Prepare(query)
12391248
if err != nil {
@@ -1326,7 +1335,7 @@ func Test_sqlmock_Query(t *testing.T) {
13261335
}
13271336
defer db.Close()
13281337
expectedRows := mock.NewRows([]string{"id", "name", "email"}).AddRow(1, "test", "[email protected]")
1329-
mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WithArgs("test").WillReturnRows(expectedRows)
1338+
mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WillReturnRows(expectedRows)
13301339
query := "SELECT name, email FROM users WHERE name = ?"
13311340
rows, err := mock.(*sqlmock).Query(query, []driver.Value{"test"})
13321341
if err != nil {
@@ -1340,3 +1349,19 @@ func Test_sqlmock_Query(t *testing.T) {
13401349
return
13411350
}
13421351
}
1352+
1353+
func Test_sqlmock_QueryExpectWithoutArgs(t *testing.T) {
1354+
db, mock, err := New()
1355+
if err != nil {
1356+
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
1357+
}
1358+
defer db.Close()
1359+
expectedRows := mock.NewRows([]string{"id", "name", "email"}).AddRow(1, "test", "[email protected]")
1360+
mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WillReturnRows(expectedRows).WithoutArgs()
1361+
query := "SELECT name, email FROM users WHERE name = ?"
1362+
_, err = mock.(*sqlmock).Query(query, []driver.Value{"test"})
1363+
if err == nil {
1364+
t.Errorf("error expected")
1365+
return
1366+
}
1367+
}

0 commit comments

Comments
 (0)