From 8e6be87ade176982c515c88a7ed936000763787f Mon Sep 17 00:00:00 2001 From: "koki.hatano" Date: Tue, 8 Sep 2020 08:39:30 +0900 Subject: [PATCH 01/17] :sparkles: Add go 1.15 in travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1312df7..d1953d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ go: - 1.12.x - 1.13.x - 1.14.x + - 1.15.x script: - go vet From c35a79d51888affd0b44e9d30d4ea8ab9628b548 Mon Sep 17 00:00:00 2001 From: ashhadsheikh Date: Mon, 2 Nov 2020 18:02:08 +0500 Subject: [PATCH 02/17] Update code sample --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4151f4..8cf8717 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ import ( ) func recordStats(db *sql.DB, userID, productID int64) (err error) { - tx, err = db.Begin() + tx, err := db.Begin() if err != nil { return } From ad48e464a0a996093b631791187840927a1516e1 Mon Sep 17 00:00:00 2001 From: "Alan D. Cabrera" Date: Fri, 15 Jan 2021 07:02:35 -0800 Subject: [PATCH 03/17] Fix ExpectedExec Stringer implementation Sometimes the result is incorrectly set, so the cast that takes place in the String() method returns nil. --- expectations.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/expectations.go b/expectations.go index 5c82c7b..5adf608 100644 --- a/expectations.go +++ b/expectations.go @@ -230,12 +230,13 @@ func (e *ExpectedExec) String() string { } if e.result != nil { - res, _ := e.result.(*result) - msg += "\n - should return Result having:" - msg += fmt.Sprintf("\n LastInsertId: %d", res.insertID) - msg += fmt.Sprintf("\n RowsAffected: %d", res.rowsAffected) - if res.err != nil { - msg += fmt.Sprintf("\n Error: %s", res.err) + if res, ok := e.result.(*result); ok { + msg += "\n - should return Result having:" + msg += fmt.Sprintf("\n LastInsertId: %d", res.insertID) + msg += fmt.Sprintf("\n RowsAffected: %d", res.rowsAffected) + if res.err != nil { + msg += fmt.Sprintf("\n Error: %s", res.err) + } } } From 35c0d3c10bef576babf8d426acc68df264dc4bbd Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 30 May 2021 13:15:32 +0530 Subject: [PATCH 04/17] add dependency to go.mod --- go.mod | 4 ++++ go.sum | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 go.sum diff --git a/go.mod b/go.mod index eaf8a5a..2c288f8 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,5 @@ module github.com/DATA-DOG/go-sqlmock + +go 1.16 + +require github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a21f637 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= From 2df2d8867c69b8c31a0a98e0f7984e5d4e3ea149 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 30 May 2021 13:15:50 +0530 Subject: [PATCH 05/17] add AddRows function to allow adding multiple rows --- rows.go | 10 +++++++ rows_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/rows.go b/rows.go index ccc5f0c..941544b 100644 --- a/rows.go +++ b/rows.go @@ -188,6 +188,16 @@ func (r *Rows) AddRow(values ...driver.Value) *Rows { return r } +// AddRows adds multiple rows composed from database driver.Value slice and +// returns the same instance to perform subsequent actions. +func (r *Rows) AddRows(values ...[]driver.Value) *Rows { + for _, value := range values { + r.AddRow(value...) + } + + return r +} + // FromCSVString build rows from csv string. // return the same instance to perform subsequent actions. // Note that the number of values must match the number diff --git a/rows_test.go b/rows_test.go index 15cdbee..9d2c7e1 100644 --- a/rows_test.go +++ b/rows_test.go @@ -3,6 +3,7 @@ package sqlmock import ( "bytes" "database/sql" + "database/sql/driver" "fmt" "testing" ) @@ -670,3 +671,85 @@ func queryRowBytesNotInvalidatedByClose(t *testing.T, rows *Rows, scan func(*sql t.Fatal(err) } } + +func TestAddRows(t *testing.T) { + t.Parallel() + db, mock, err := New() + if err != nil { + t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + + values := [][]driver.Value{ + { + 1, "John", + }, + { + 2, "Jane", + }, + { + 3, "Peter", + }, + { + 4, "Emily", + }, + } + + rows := NewRows([]string{"id", "name"}).AddRows(values...) + mock.ExpectQuery("SELECT").WillReturnRows(rows).RowsWillBeClosed() + + rs, _ := db.Query("SELECT") + defer rs.Close() + + for rs.Next() { + var id int + var name string + rs.Scan(&id, &name) + fmt.Println("scanned id:", id, "and name:", name) + } + + if rs.Err() != nil { + fmt.Println("got rows error:", rs.Err()) + } + // Output: scanned id: 1 and title: John + // scanned id: 2 and title: Jane + // scanned id: 3 and title: Peter + // scanned id: 4 and title: Emily +} + +func ExampleMultiRows() { + db, mock, err := New() + if err != nil { + fmt.Println("failed to open sqlmock database:", err) + } + defer db.Close() + + values := [][]driver.Value{ + { + 1, "one", + }, + { + 2, "two", + }, + } + + rows := NewRows([]string{"id", "title"}).AddRows(values...) + + mock.ExpectQuery("SELECT").WillReturnRows(rows) + + rs, _ := db.Query("SELECT") + defer rs.Close() + + for rs.Next() { + var id int + var title string + rs.Scan(&id, &title) + fmt.Println("scanned id:", id, "and title:", title) + } + + if rs.Err() != nil { + fmt.Println("got rows error:", rs.Err()) + } + // Output: scanned id: 1 and title: one + // scanned id: 2 and title: two +} From caefcfd920cffeed4cf1c86cb4049f6fef661a7e Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 30 May 2021 13:19:48 +0530 Subject: [PATCH 06/17] support go 1.15 go versions 1.14 and below are unsupported as per https://golang.org/doc/devel/release#policy and hence the library does not support them either --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2c288f8..6f58b70 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/DATA-DOG/go-sqlmock -go 1.16 +go 1.15 require github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 From 4d55f937d066da43dc7917fe15bbd7c931de2114 Mon Sep 17 00:00:00 2001 From: Ajitem Sahasrabuddhe Date: Sun, 30 May 2021 13:21:28 +0530 Subject: [PATCH 07/17] fix go vet error --- rows_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rows_test.go b/rows_test.go index 9d2c7e1..ef17521 100644 --- a/rows_test.go +++ b/rows_test.go @@ -717,7 +717,7 @@ func TestAddRows(t *testing.T) { // scanned id: 4 and title: Emily } -func ExampleMultiRows() { +func ExampleRows_AddRows() { db, mock, err := New() if err != nil { fmt.Println("failed to open sqlmock database:", err) From fc9f7daa31e2fe8a41ba97624fe7e9826017cd30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Wed, 17 Nov 2021 21:33:56 -0500 Subject: [PATCH 08/17] Add Go 1.16 and 1.17 to Travis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gábor Lipták --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d1953d2..5594029 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,8 @@ go: - 1.13.x - 1.14.x - 1.15.x + - 1.16.x + - 1.17.x script: - go vet From 742d0bc5c892b46187a88ec25910bcffd868c937 Mon Sep 17 00:00:00 2001 From: bearname Date: Mon, 7 Feb 2022 15:03:11 +0300 Subject: [PATCH 09/17] fix package --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8cf8717..34da8f0 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ func (a AnyTime) Match(v driver.Value) bool { func TestAnyTimeArgument(t *testing.T) { t.Parallel() - db, mock, err := New() + db, mock, err := sqlmock.New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } @@ -201,7 +201,7 @@ func TestAnyTimeArgument(t *testing.T) { mock.ExpectExec("INSERT INTO users"). WithArgs("john", AnyTime{}). - WillReturnResult(NewResult(1, 1)) + WillReturnResult(sqlmock.NewResult(1, 1)) _, err = db.Exec("INSERT INTO users(name, created_at) VALUES (?, ?)", "john", time.Now()) if err != nil { From 33a3e3a1b4607245786f5defa34a0908dc8f24cf Mon Sep 17 00:00:00 2001 From: Ghvstcode Date: Fri, 24 Jun 2022 21:14:31 +0100 Subject: [PATCH 10/17] add issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 22 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 +++++ .github/ISSUE_TEMPLATE/feature_request.md | 26 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/question.md | 11 ++++++++++ 4 files changed, 64 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..777c734 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,22 @@ +--- +name: Bug Report +about: Let us know about an unexpected error, or an incorrect behavior. +labels: "type/bug" +--- + + +### Operating system and Go Version + +### Issue + +### Reproduction steps + +#### Expected Result + +#### Actual Result + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..6d35c11 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Curious About Something? + url: https://pkg.go.dev/github.com/DATA-DOG/go-sqlmock + about: If you have a curious about something that isn't totally clear, please checkout the documentation first before creating an issue. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..4a4a7f7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,26 @@ +--- +name: Feature Request +about: Suggest a new feature or enhancement to this project +labels: "type/enhancement" +--- + + + +### Proposal + + +### Use-cases + + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..cf68c29 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,11 @@ +--- +name: Question +about: Ask a question +labels: "type/question" +--- + +### Question + + From 37b1bab1c0d6cf90c86e026bbc47894ca84d2af7 Mon Sep 17 00:00:00 2001 From: Ivo Gosemann Date: Mon, 1 Aug 2022 10:39:40 +0200 Subject: [PATCH 11/17] Fixes expectations no arg exp but act args passed --- expectations_go18.go | 3 +++ expectations_go18_test.go | 8 ++++---- sqlmock_go18_test.go | 4 ++-- sqlmock_test.go | 14 +++++++------- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/expectations_go18.go b/expectations_go18.go index 6b85ce1..767ebd4 100644 --- a/expectations_go18.go +++ b/expectations_go18.go @@ -30,6 +30,9 @@ func (e *ExpectedQuery) WillReturnRows(rows ...*Rows) *ExpectedQuery { func (e *queryBasedExpectation) argsMatches(args []driver.NamedValue) error { if nil == e.args { + if len(args) > 0 { + return fmt.Errorf("expected 0, but got %d arguments", len(args)) + } return nil } if len(args) != len(e.args) { diff --git a/expectations_go18_test.go b/expectations_go18_test.go index 1974721..3e8821c 100644 --- a/expectations_go18_test.go +++ b/expectations_go18_test.go @@ -12,8 +12,8 @@ import ( func TestQueryExpectationArgComparison(t *testing.T) { e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} against := []driver.NamedValue{{Value: int64(5), Ordinal: 1}} - if err := e.argsMatches(against); err != nil { - t.Errorf("arguments should match, since the no expectation was set, but got err: %s", err) + if err := e.argsMatches(against); err == nil { + t.Errorf("arguments should not match, since no expectation was set, but argument was passed") } e.args = []driver.Value{5, "str"} @@ -104,8 +104,8 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) { func TestQueryExpectationNamedArgComparison(t *testing.T) { e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} against := []driver.NamedValue{{Value: int64(5), Name: "id"}} - if err := e.argsMatches(against); err != nil { - t.Errorf("arguments should match, since the no expectation was set, but got err: %s", err) + if err := e.argsMatches(against); err == nil { + t.Errorf("arguments should not match, since no expectation was set, but argument was passed") } e.args = []driver.Value{ diff --git a/sqlmock_go18_test.go b/sqlmock_go18_test.go index 223e076..cf56e67 100644 --- a/sqlmock_go18_test.go +++ b/sqlmock_go18_test.go @@ -435,9 +435,9 @@ func TestContextExecErrorDelay(t *testing.T) { defer db.Close() // test that return of error is delayed - var delay time.Duration - delay = 100 * time.Millisecond + var delay time.Duration = 100 * time.Millisecond mock.ExpectExec("^INSERT INTO articles"). + WithArgs("hello"). WillReturnError(errors.New("slow fail")). WillDelayFor(delay) diff --git a/sqlmock_test.go b/sqlmock_test.go index ee6b516..982a32a 100644 --- a/sqlmock_test.go +++ b/sqlmock_test.go @@ -959,7 +959,7 @@ func TestPrepareExec(t *testing.T) { mock.ExpectBegin() ep := mock.ExpectPrepare("INSERT INTO ORDERS\\(ID, STATUS\\) VALUES \\(\\?, \\?\\)") for i := 0; i < 3; i++ { - ep.ExpectExec().WillReturnResult(NewResult(1, 1)) + ep.ExpectExec().WithArgs(i, "Hello"+strconv.Itoa(i)).WillReturnResult(NewResult(1, 1)) } mock.ExpectCommit() tx, _ := db.Begin() @@ -1073,7 +1073,7 @@ func TestPreparedStatementCloseExpectation(t *testing.T) { defer db.Close() ep := mock.ExpectPrepare("INSERT INTO ORDERS").WillBeClosed() - ep.ExpectExec().WillReturnResult(NewResult(1, 1)) + ep.ExpectExec().WithArgs(1, "Hello").WillReturnResult(NewResult(1, 1)) stmt, err := db.Prepare("INSERT INTO ORDERS(ID, STATUS) VALUES (?, ?)") if err != nil { @@ -1102,9 +1102,9 @@ func TestExecExpectationErrorDelay(t *testing.T) { defer db.Close() // test that return of error is delayed - var delay time.Duration - delay = 100 * time.Millisecond + var delay time.Duration = 100 * time.Millisecond mock.ExpectExec("^INSERT INTO articles"). + WithArgs("hello"). WillReturnError(errors.New("slow fail")). WillDelayFor(delay) @@ -1230,10 +1230,10 @@ func Test_sqlmock_Prepare_and_Exec(t *testing.T) { mock.ExpectPrepare("SELECT (.+) FROM users WHERE (.+)") expected := NewResult(1, 1) - mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)"). + mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)").WithArgs("test"). WillReturnResult(expected) expectedRows := mock.NewRows([]string{"id", "name", "email"}).AddRow(1, "test", "test@example.com") - mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WillReturnRows(expectedRows) + mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WithArgs("test").WillReturnRows(expectedRows) got, err := mock.(*sqlmock).Prepare(query) if err != nil { @@ -1326,7 +1326,7 @@ func Test_sqlmock_Query(t *testing.T) { } defer db.Close() expectedRows := mock.NewRows([]string{"id", "name", "email"}).AddRow(1, "test", "test@example.com") - mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WillReturnRows(expectedRows) + mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WithArgs("test").WillReturnRows(expectedRows) query := "SELECT name, email FROM users WHERE name = ?" rows, err := mock.(*sqlmock).Query(query, []driver.Value{"test"}) if err != nil { From 7c9a431ac4e5774d5a103983c16d56626b911a3d Mon Sep 17 00:00:00 2001 From: Ivo Gosemann Date: Mon, 1 Aug 2022 11:07:26 +0200 Subject: [PATCH 12/17] Adds fix to before go 1.8 --- expectations_before_go18.go | 3 +++ expectations_before_go18_test.go | 4 ++-- expectations_go18_test.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/expectations_before_go18.go b/expectations_before_go18.go index f6e7b4e..0831863 100644 --- a/expectations_before_go18.go +++ b/expectations_before_go18.go @@ -17,6 +17,9 @@ func (e *ExpectedQuery) WillReturnRows(rows *Rows) *ExpectedQuery { func (e *queryBasedExpectation) argsMatches(args []namedValue) error { if nil == e.args { + if len(args) > 0 { + return fmt.Errorf("expected 0, but got %d arguments", len(args)) + } return nil } if len(args) != len(e.args) { diff --git a/expectations_before_go18_test.go b/expectations_before_go18_test.go index 897ebff..81dc8cf 100644 --- a/expectations_before_go18_test.go +++ b/expectations_before_go18_test.go @@ -11,8 +11,8 @@ import ( func TestQueryExpectationArgComparison(t *testing.T) { e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} against := []namedValue{{Value: int64(5), Ordinal: 1}} - if err := e.argsMatches(against); err != nil { - t.Errorf("arguments should match, since the no expectation was set, but got err: %s", err) + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since no expectation was set, but argument was passed") } e.args = []driver.Value{5, "str"} diff --git a/expectations_go18_test.go b/expectations_go18_test.go index 3e8821c..d5638bc 100644 --- a/expectations_go18_test.go +++ b/expectations_go18_test.go @@ -13,7 +13,7 @@ func TestQueryExpectationArgComparison(t *testing.T) { e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} against := []driver.NamedValue{{Value: int64(5), Ordinal: 1}} if err := e.argsMatches(against); err == nil { - t.Errorf("arguments should not match, since no expectation was set, but argument was passed") + t.Error("arguments should not match, since no expectation was set, but argument was passed") } e.args = []driver.Value{5, "str"} From a1ad26d1102880d2124d764aa0a80707f95dffee Mon Sep 17 00:00:00 2001 From: Ivo Gosemann Date: Thu, 4 May 2023 19:19:09 +0200 Subject: [PATCH 13/17] fixes csv parse errors being silently ignored --- rows.go | 8 ++++++-- rows_test.go | 9 +++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/rows.go b/rows.go index 941544b..24b1bcd 100644 --- a/rows.go +++ b/rows.go @@ -4,6 +4,7 @@ import ( "bytes" "database/sql/driver" "encoding/csv" + "errors" "fmt" "io" "strings" @@ -208,8 +209,11 @@ func (r *Rows) FromCSVString(s string) *Rows { for { res, err := csvReader.Read() - if err != nil || res == nil { - break + if err != nil { + if errors.Is(err, io.EOF) { + break + } + panic(fmt.Sprintf("Parsing CSV string failed: %s", err.Error())) } row := make([]driver.Value, len(r.cols)) diff --git a/rows_test.go b/rows_test.go index ef17521..c2a9ebe 100644 --- a/rows_test.go +++ b/rows_test.go @@ -461,6 +461,15 @@ func TestCSVRowParser(t *testing.T) { } } +func TestCSVParserInvalidInput(t *testing.T) { + defer func() { + recover() + }() + _ = NewRows([]string{"col1", "col2"}).FromCSVString("a,\"NULL\"\"") + // shouldn't reach here + t.Error("expected panic from parsing invalid CSV") +} + func TestWrongNumberOfValues(t *testing.T) { // Open new mock database db, mock, err := New() From fd971def423315aacad9923a9a9eb8e4ab86ce11 Mon Sep 17 00:00:00 2001 From: Ivo Gosemann Date: Wed, 9 Aug 2023 17:19:35 +0200 Subject: [PATCH 14/17] CSVColParser: correctly set nil values in Rows --- rows.go | 2 +- rows_test.go | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/rows.go b/rows.go index 24b1bcd..3e916ca 100644 --- a/rows.go +++ b/rows.go @@ -15,7 +15,7 @@ const invalidate = "☠☠☠ MEMORY OVERWRITTEN ☠☠☠ " // CSVColumnParser is a function which converts trimmed csv // column string to a []byte representation. Currently // transforms NULL to nil -var CSVColumnParser = func(s string) []byte { +var CSVColumnParser = func(s string) interface{} { switch { case strings.ToLower(s) == "null": return nil diff --git a/rows_test.go b/rows_test.go index c2a9ebe..3a5afbb 100644 --- a/rows_test.go +++ b/rows_test.go @@ -432,7 +432,7 @@ func TestRowsScanError(t *testing.T) { func TestCSVRowParser(t *testing.T) { t.Parallel() - rs := NewRows([]string{"col1", "col2"}).FromCSVString("a,NULL") + rs := NewRows([]string{"col1", "col2", "col3"}).FromCSVString("a,NULL,NULL") db, mock, err := New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) @@ -448,9 +448,10 @@ func TestCSVRowParser(t *testing.T) { defer rw.Close() var col1 string var col2 []byte + var col3 *string rw.Next() - if err = rw.Scan(&col1, &col2); err != nil { + if err = rw.Scan(&col1, &col2, &col3); err != nil { t.Fatalf("unexpected error: %s", err) } if col1 != "a" { @@ -459,6 +460,9 @@ func TestCSVRowParser(t *testing.T) { if col2 != nil { t.Fatalf("expected col2 to be nil, but got [%T]:%+v", col2, col2) } + if col3 != nil { + t.Fatalf("expected col3 to be nil, but got [%T]:%+v", col2, col2) + } } func TestCSVParserInvalidInput(t *testing.T) { From 4a9308e2e87ec768ea28c81f46fdf3543454c16c Mon Sep 17 00:00:00 2001 From: IvoGoman Date: Wed, 9 Aug 2023 23:43:51 +0200 Subject: [PATCH 15/17] Update rows_test.go Co-authored-by: Jessie A. Morris --- rows_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rows_test.go b/rows_test.go index 3a5afbb..e2d57ec 100644 --- a/rows_test.go +++ b/rows_test.go @@ -461,7 +461,7 @@ func TestCSVRowParser(t *testing.T) { t.Fatalf("expected col2 to be nil, but got [%T]:%+v", col2, col2) } if col3 != nil { - t.Fatalf("expected col3 to be nil, but got [%T]:%+v", col2, col2) + t.Fatalf("expected col3 to be nil, but got [%T]:%+v", col3, col3) } } From e4270300ef8b76cff7217fbe408b28adbf52fde6 Mon Sep 17 00:00:00 2001 From: Mae Kennedy Date: Thu, 19 Oct 2023 17:24:06 +0000 Subject: [PATCH 16/17] Modify: existing panic in AddRow to give a hint to the issue --- rows.go | 2 +- rows_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/rows.go b/rows.go index 3e916ca..01ea811 100644 --- a/rows.go +++ b/rows.go @@ -166,7 +166,7 @@ func (r *Rows) RowError(row int, err error) *Rows { // of columns func (r *Rows) AddRow(values ...driver.Value) *Rows { if len(values) != len(r.cols) { - panic("Expected number of values to match number of columns") + panic(fmt.Sprintf("Expected number of values to match number of columns: expected %d, actual %d", len(values), len(r.cols))) } row := make([]driver.Value, len(r.cols)) diff --git a/rows_test.go b/rows_test.go index e2d57ec..80f1476 100644 --- a/rows_test.go +++ b/rows_test.go @@ -730,6 +730,31 @@ func TestAddRows(t *testing.T) { // scanned id: 4 and title: Emily } +func TestAddRowExpectPanic(t *testing.T) { + t.Parallel() + + const expectedPanic = "Expected number of values to match number of columns: expected 1, actual 2" + values := []driver.Value{ + "John", + "Jane", + } + + defer func() { + if r := recover(); r != nil { + if r != expectedPanic { + t.Fatalf("panic message did not match expected: expected '%s', actual '%s'", r, expectedPanic) + } + + return + } + t.Fatalf("expected panic: %s", expectedPanic) + }() + + rows := NewRows([]string{"id", "name"}) + // Note missing spread "..." + rows.AddRow(values) +} + func ExampleRows_AddRows() { db, mock, err := New() if err != nil { From a6a27b71b91fa833474353571363d22b4541c687 Mon Sep 17 00:00:00 2001 From: Ivo Gosemann Date: Mon, 11 Dec 2023 17:33:56 +0100 Subject: [PATCH 17/17] fix: make no argument passed validation opt-in --- expectations.go | 33 ++++++++++++++++++++++++++++ expectations_before_go18.go | 3 ++- expectations_before_go18_test.go | 10 +++++++-- expectations_go18.go | 3 ++- expectations_go18_test.go | 19 ++++++++++++---- expectations_test.go | 22 +++++++++++++++++++ sqlmock_go18_test.go | 2 +- sqlmock_test.go | 37 ++++++++++++++++++++++++++------ 8 files changed, 114 insertions(+), 15 deletions(-) diff --git a/expectations.go b/expectations.go index 5adf608..8a6cd44 100644 --- a/expectations.go +++ b/expectations.go @@ -134,11 +134,27 @@ type ExpectedQuery struct { // WithArgs will match given expected args to actual database query arguments. // if at least one argument does not match, it will return an error. For specific // arguments an sqlmock.Argument interface can be used to match an argument. +// Must not be used together with WithoutArgs() func (e *ExpectedQuery) WithArgs(args ...driver.Value) *ExpectedQuery { + if e.noArgs { + panic("WithArgs() and WithoutArgs() must not be used together") + } e.args = args return e } +// WithoutArgs will ensure that no arguments are passed for this query. +// if at least one argument is passed, it will return an error. This allows +// for stricter validation of the query arguments. +// Must no be used together with WithArgs() +func (e *ExpectedQuery) WithoutArgs() *ExpectedQuery { + if len(e.args) > 0 { + panic("WithoutArgs() and WithArgs() must not be used together") + } + e.noArgs = true + return e +} + // RowsWillBeClosed expects this query rows to be closed. func (e *ExpectedQuery) RowsWillBeClosed() *ExpectedQuery { e.rowsMustBeClosed = true @@ -195,11 +211,27 @@ type ExpectedExec struct { // WithArgs will match given expected args to actual database exec operation arguments. // if at least one argument does not match, it will return an error. For specific // arguments an sqlmock.Argument interface can be used to match an argument. +// Must not be used together with WithoutArgs() func (e *ExpectedExec) WithArgs(args ...driver.Value) *ExpectedExec { + if len(e.args) > 0 { + panic("WithArgs() and WithoutArgs() must not be used together") + } e.args = args return e } +// WithoutArgs will ensure that no args are passed for this expected database exec action. +// if at least one argument is passed, it will return an error. This allows for stricter +// validation of the query arguments. +// Must not be used together with WithArgs() +func (e *ExpectedExec) WithoutArgs() *ExpectedExec { + if len(e.args) > 0 { + panic("WithoutArgs() and WithArgs() must not be used together") + } + e.noArgs = true + return e +} + // WillReturnError allows to set an error for expected database exec action func (e *ExpectedExec) WillReturnError(err error) *ExpectedExec { e.err = err @@ -338,6 +370,7 @@ type queryBasedExpectation struct { expectSQL string converter driver.ValueConverter args []driver.Value + noArgs bool // ensure no args are passed } // ExpectedPing is used to manage *sql.DB.Ping expectations. diff --git a/expectations_before_go18.go b/expectations_before_go18.go index 0831863..67c08dc 100644 --- a/expectations_before_go18.go +++ b/expectations_before_go18.go @@ -1,3 +1,4 @@ +//go:build !go1.8 // +build !go1.8 package sqlmock @@ -17,7 +18,7 @@ func (e *ExpectedQuery) WillReturnRows(rows *Rows) *ExpectedQuery { func (e *queryBasedExpectation) argsMatches(args []namedValue) error { if nil == e.args { - if len(args) > 0 { + if e.noArgs && len(args) > 0 { return fmt.Errorf("expected 0, but got %d arguments", len(args)) } return nil diff --git a/expectations_before_go18_test.go b/expectations_before_go18_test.go index 81dc8cf..4234cd6 100644 --- a/expectations_before_go18_test.go +++ b/expectations_before_go18_test.go @@ -1,3 +1,4 @@ +//go:build !go1.8 // +build !go1.8 package sqlmock @@ -9,10 +10,15 @@ import ( ) func TestQueryExpectationArgComparison(t *testing.T) { - e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} + e := &queryBasedExpectation{converter: driver.DefaultParameterConverter, noArgs: true} against := []namedValue{{Value: int64(5), Ordinal: 1}} if err := e.argsMatches(against); err == nil { - t.Error("arguments should not match, since no expectation was set, but argument was passed") + t.Error("arguments should not match, since argument was passed, but noArgs was set") + } + + e.noArgs = false + if err := e.argsMatches(against); err != nil { + t.Error("arguments should match, since argument was passed, but no expected args or noArgs was set") } e.args = []driver.Value{5, "str"} diff --git a/expectations_go18.go b/expectations_go18.go index 767ebd4..07227ed 100644 --- a/expectations_go18.go +++ b/expectations_go18.go @@ -1,3 +1,4 @@ +//go:build go1.8 // +build go1.8 package sqlmock @@ -30,7 +31,7 @@ func (e *ExpectedQuery) WillReturnRows(rows ...*Rows) *ExpectedQuery { func (e *queryBasedExpectation) argsMatches(args []driver.NamedValue) error { if nil == e.args { - if len(args) > 0 { + if e.noArgs && len(args) > 0 { return fmt.Errorf("expected 0, but got %d arguments", len(args)) } return nil diff --git a/expectations_go18_test.go b/expectations_go18_test.go index d5638bc..cd633b7 100644 --- a/expectations_go18_test.go +++ b/expectations_go18_test.go @@ -1,3 +1,4 @@ +//go:build go1.8 // +build go1.8 package sqlmock @@ -10,10 +11,15 @@ import ( ) func TestQueryExpectationArgComparison(t *testing.T) { - e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} + e := &queryBasedExpectation{converter: driver.DefaultParameterConverter, noArgs: true} against := []driver.NamedValue{{Value: int64(5), Ordinal: 1}} if err := e.argsMatches(against); err == nil { - t.Error("arguments should not match, since no expectation was set, but argument was passed") + t.Error("arguments should not match, since argument was passed, but noArgs was set") + } + + e.noArgs = false + if err := e.argsMatches(against); err != nil { + t.Error("arguments should match, since argument was passed, but no expected args or noArgs was set") } e.args = []driver.Value{5, "str"} @@ -102,10 +108,15 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) { } func TestQueryExpectationNamedArgComparison(t *testing.T) { - e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} + e := &queryBasedExpectation{converter: driver.DefaultParameterConverter, noArgs: true} against := []driver.NamedValue{{Value: int64(5), Name: "id"}} if err := e.argsMatches(against); err == nil { - t.Errorf("arguments should not match, since no expectation was set, but argument was passed") + t.Error("arguments should not match, since argument was passed, but noArgs was set") + } + + e.noArgs = false + if err := e.argsMatches(against); err != nil { + t.Error("arguments should match, since argument was passed, but no expected args or noArgs was set") } e.args = []driver.Value{ diff --git a/expectations_test.go b/expectations_test.go index afda582..cf0251a 100644 --- a/expectations_test.go +++ b/expectations_test.go @@ -101,3 +101,25 @@ func TestCustomValueConverterQueryScan(t *testing.T) { t.Error(err) } } + +func TestQueryWithNoArgsAndWithArgsPanic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + return + } + t.Error("Expected panic for using WithArgs and ExpectNoArgs together") + }() + mock := &sqlmock{} + mock.ExpectQuery("SELECT (.+) FROM user").WithArgs("John").WithoutArgs() +} + +func TestExecWithNoArgsAndWithArgsPanic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + return + } + t.Error("Expected panic for using WithArgs and ExpectNoArgs together") + }() + mock := &sqlmock{} + mock.ExpectExec("^INSERT INTO user").WithArgs("John").WithoutArgs() +} diff --git a/sqlmock_go18_test.go b/sqlmock_go18_test.go index cf56e67..6267f38 100644 --- a/sqlmock_go18_test.go +++ b/sqlmock_go18_test.go @@ -1,3 +1,4 @@ +//go:build go1.8 // +build go1.8 package sqlmock @@ -437,7 +438,6 @@ func TestContextExecErrorDelay(t *testing.T) { // test that return of error is delayed var delay time.Duration = 100 * time.Millisecond mock.ExpectExec("^INSERT INTO articles"). - WithArgs("hello"). WillReturnError(errors.New("slow fail")). WillDelayFor(delay) diff --git a/sqlmock_test.go b/sqlmock_test.go index 982a32a..2129a16 100644 --- a/sqlmock_test.go +++ b/sqlmock_test.go @@ -749,6 +749,16 @@ func TestRunExecsWithExpectedErrorMeetsExpectations(t *testing.T) { } } +func TestRunExecsWithNoArgsExpectedMeetsExpectations(t *testing.T) { + db, dbmock, _ := New() + dbmock.ExpectExec("THE FIRST EXEC").WithoutArgs().WillReturnResult(NewResult(0, 0)) + + _, err := db.Exec("THE FIRST EXEC", "foobar") + if err == nil { + t.Fatalf("expected error, but there wasn't any") + } +} + func TestRunQueryWithExpectedErrorMeetsExpectations(t *testing.T) { db, dbmock, _ := New() dbmock.ExpectQuery("THE FIRST QUERY").WillReturnError(fmt.Errorf("big bad bug")) @@ -959,7 +969,7 @@ func TestPrepareExec(t *testing.T) { mock.ExpectBegin() ep := mock.ExpectPrepare("INSERT INTO ORDERS\\(ID, STATUS\\) VALUES \\(\\?, \\?\\)") for i := 0; i < 3; i++ { - ep.ExpectExec().WithArgs(i, "Hello"+strconv.Itoa(i)).WillReturnResult(NewResult(1, 1)) + ep.ExpectExec().WillReturnResult(NewResult(1, 1)) } mock.ExpectCommit() tx, _ := db.Begin() @@ -1073,7 +1083,7 @@ func TestPreparedStatementCloseExpectation(t *testing.T) { defer db.Close() ep := mock.ExpectPrepare("INSERT INTO ORDERS").WillBeClosed() - ep.ExpectExec().WithArgs(1, "Hello").WillReturnResult(NewResult(1, 1)) + ep.ExpectExec().WillReturnResult(NewResult(1, 1)) stmt, err := db.Prepare("INSERT INTO ORDERS(ID, STATUS) VALUES (?, ?)") if err != nil { @@ -1104,7 +1114,6 @@ func TestExecExpectationErrorDelay(t *testing.T) { // test that return of error is delayed var delay time.Duration = 100 * time.Millisecond mock.ExpectExec("^INSERT INTO articles"). - WithArgs("hello"). WillReturnError(errors.New("slow fail")). WillDelayFor(delay) @@ -1230,10 +1239,10 @@ func Test_sqlmock_Prepare_and_Exec(t *testing.T) { mock.ExpectPrepare("SELECT (.+) FROM users WHERE (.+)") expected := NewResult(1, 1) - mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)").WithArgs("test"). + mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)"). WillReturnResult(expected) expectedRows := mock.NewRows([]string{"id", "name", "email"}).AddRow(1, "test", "test@example.com") - mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WithArgs("test").WillReturnRows(expectedRows) + mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WillReturnRows(expectedRows) got, err := mock.(*sqlmock).Prepare(query) if err != nil { @@ -1326,7 +1335,7 @@ func Test_sqlmock_Query(t *testing.T) { } defer db.Close() expectedRows := mock.NewRows([]string{"id", "name", "email"}).AddRow(1, "test", "test@example.com") - mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WithArgs("test").WillReturnRows(expectedRows) + mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WillReturnRows(expectedRows) query := "SELECT name, email FROM users WHERE name = ?" rows, err := mock.(*sqlmock).Query(query, []driver.Value{"test"}) if err != nil { @@ -1340,3 +1349,19 @@ func Test_sqlmock_Query(t *testing.T) { return } } + +func Test_sqlmock_QueryExpectWithoutArgs(t *testing.T) { + db, mock, err := New() + if err != nil { + t.Errorf("an error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() + expectedRows := mock.NewRows([]string{"id", "name", "email"}).AddRow(1, "test", "test@example.com") + mock.ExpectQuery("SELECT (.+) FROM users WHERE (.+)").WillReturnRows(expectedRows).WithoutArgs() + query := "SELECT name, email FROM users WHERE name = ?" + _, err = mock.(*sqlmock).Query(query, []driver.Value{"test"}) + if err == nil { + t.Errorf("error expected") + return + } +}