From 2ef7c147be19db7692887df3ee95fbfa65ecd9bc Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Thu, 28 Nov 2019 13:51:27 +0300 Subject: [PATCH 01/14] migration from custom namedValue to driver.NamedValue --- .gitignore | 1 + expectations.go | 2 +- expectations_before_go18.go | 2 +- expectations_go18.go | 3 ++- expectations_go18_test.go | 10 +++++----- expectations_test.go | 20 ++++++++++---------- sqlmock.go | 18 ++++++------------ sqlmock_go18.go | 14 ++------------ sqlmock_go19.go | 2 ++ 9 files changed, 30 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index e4001c0..0e5426a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /examples/blog/blog /examples/orders/orders /examples/basic/basic +.idea/ diff --git a/expectations.go b/expectations.go index 38c0d17..b36470f 100644 --- a/expectations.go +++ b/expectations.go @@ -339,7 +339,7 @@ type queryBasedExpectation struct { args []driver.Value } -func (e *queryBasedExpectation) attemptArgMatch(args []namedValue) (err error) { +func (e *queryBasedExpectation) attemptArgMatch(args []driver.NamedValue) (err error) { // catch panic defer func() { if e := recover(); e != nil { diff --git a/expectations_before_go18.go b/expectations_before_go18.go index e368e04..62d885b 100644 --- a/expectations_before_go18.go +++ b/expectations_before_go18.go @@ -15,7 +15,7 @@ func (e *ExpectedQuery) WillReturnRows(rows *Rows) *ExpectedQuery { return e } -func (e *queryBasedExpectation) argsMatches(args []namedValue) error { +func (e *queryBasedExpectation) argsMatches(args []driver.NamedValue) error { if nil == e.args { return nil } diff --git a/expectations_go18.go b/expectations_go18.go index 6ee8adf..8f4d23c 100644 --- a/expectations_go18.go +++ b/expectations_go18.go @@ -4,6 +4,7 @@ package sqlmock import ( "database/sql" + "database/sql/driver" "fmt" "reflect" ) @@ -19,7 +20,7 @@ func (e *ExpectedQuery) WillReturnRows(rows ...*Rows) *ExpectedQuery { return e } -func (e *queryBasedExpectation) argsMatches(args []namedValue) error { +func (e *queryBasedExpectation) argsMatches(args []driver.NamedValue) error { if nil == e.args { return nil } diff --git a/expectations_go18_test.go b/expectations_go18_test.go index 2b85db3..c9bed7f 100644 --- a/expectations_go18_test.go +++ b/expectations_go18_test.go @@ -10,7 +10,7 @@ import ( func TestQueryExpectationNamedArgComparison(t *testing.T) { e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} - against := []namedValue{{Value: int64(5), Name: "id"}} + 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) } @@ -24,7 +24,7 @@ func TestQueryExpectationNamedArgComparison(t *testing.T) { t.Error("arguments should not match, since the size is not the same") } - against = []namedValue{ + against = []driver.NamedValue{ {Value: int64(5), Name: "id"}, {Value: "str", Name: "s"}, } @@ -33,7 +33,7 @@ func TestQueryExpectationNamedArgComparison(t *testing.T) { t.Errorf("arguments should have matched, but it did not: %v", err) } - against = []namedValue{ + against = []driver.NamedValue{ {Value: int64(5), Name: "id"}, {Value: "str", Name: "username"}, } @@ -44,7 +44,7 @@ func TestQueryExpectationNamedArgComparison(t *testing.T) { e.args = []driver.Value{int64(5), "str"} - against = []namedValue{ + against = []driver.NamedValue{ {Value: int64(5), Ordinal: 0}, {Value: "str", Ordinal: 1}, } @@ -53,7 +53,7 @@ func TestQueryExpectationNamedArgComparison(t *testing.T) { t.Error("arguments matched, but it should have not due to wrong Ordinal position") } - against = []namedValue{ + against = []driver.NamedValue{ {Value: int64(5), Ordinal: 1}, {Value: "str", Ordinal: 2}, } diff --git a/expectations_test.go b/expectations_test.go index c6889c3..7752de0 100644 --- a/expectations_test.go +++ b/expectations_test.go @@ -11,19 +11,19 @@ import ( func TestQueryExpectationArgComparison(t *testing.T) { e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} - against := []namedValue{{Value: int64(5), Ordinal: 1}} + 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) } e.args = []driver.Value{5, "str"} - against = []namedValue{{Value: int64(5), Ordinal: 1}} + against = []driver.NamedValue{{Value: int64(5), Ordinal: 1}} if err := e.argsMatches(against); err == nil { t.Error("arguments should not match, since the size is not the same") } - against = []namedValue{ + against = []driver.NamedValue{ {Value: int64(3), Ordinal: 1}, {Value: "str", Ordinal: 2}, } @@ -31,7 +31,7 @@ func TestQueryExpectationArgComparison(t *testing.T) { t.Error("arguments should not match, since the first argument (int value) is different") } - against = []namedValue{ + against = []driver.NamedValue{ {Value: int64(5), Ordinal: 1}, {Value: "st", Ordinal: 2}, } @@ -39,7 +39,7 @@ func TestQueryExpectationArgComparison(t *testing.T) { t.Error("arguments should not match, since the second argument (string value) is different") } - against = []namedValue{ + against = []driver.NamedValue{ {Value: int64(5), Ordinal: 1}, {Value: "str", Ordinal: 2}, } @@ -51,7 +51,7 @@ func TestQueryExpectationArgComparison(t *testing.T) { tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)") e.args = []driver.Value{5, tm} - against = []namedValue{ + against = []driver.NamedValue{ {Value: int64(5), Ordinal: 1}, {Value: tm, Ordinal: 2}, } @@ -69,7 +69,7 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) { var e *queryBasedExpectation e = &queryBasedExpectation{args: []driver.Value{true}, converter: driver.DefaultParameterConverter} - against := []namedValue{ + against := []driver.NamedValue{ {Value: true, Ordinal: 1}, } if err := e.argsMatches(against); err != nil { @@ -77,7 +77,7 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) { } e = &queryBasedExpectation{args: []driver.Value{false}, converter: driver.DefaultParameterConverter} - against = []namedValue{ + against = []driver.NamedValue{ {Value: false, Ordinal: 1}, } if err := e.argsMatches(against); err != nil { @@ -85,7 +85,7 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) { } e = &queryBasedExpectation{args: []driver.Value{true}, converter: driver.DefaultParameterConverter} - against = []namedValue{ + against = []driver.NamedValue{ {Value: false, Ordinal: 1}, } if err := e.argsMatches(against); err == nil { @@ -93,7 +93,7 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) { } e = &queryBasedExpectation{args: []driver.Value{false}, converter: driver.DefaultParameterConverter} - against = []namedValue{ + against = []driver.NamedValue{ {Value: true, Ordinal: 1}, } if err := e.argsMatches(against); err == nil { diff --git a/sqlmock.go b/sqlmock.go index 4896307..0ce35c4 100644 --- a/sqlmock.go +++ b/sqlmock.go @@ -247,9 +247,9 @@ func (c *sqlmock) ExpectBegin() *ExpectedBegin { // Exec meets http://golang.org/pkg/database/sql/driver/#Execer func (c *sqlmock) Exec(query string, args []driver.Value) (driver.Result, error) { - namedArgs := make([]namedValue, len(args)) + namedArgs := make([]driver.NamedValue, len(args)) for i, v := range args { - namedArgs[i] = namedValue{ + namedArgs[i] = driver.NamedValue{ Ordinal: i + 1, Value: v, } @@ -266,7 +266,7 @@ func (c *sqlmock) Exec(query string, args []driver.Value) (driver.Result, error) return ex.result, nil } -func (c *sqlmock) exec(query string, args []namedValue) (*ExpectedExec, error) { +func (c *sqlmock) exec(query string, args []driver.NamedValue) (*ExpectedExec, error) { var expected *ExpectedExec var fulfilled int var ok bool @@ -401,17 +401,11 @@ func (c *sqlmock) ExpectPrepare(expectedSQL string) *ExpectedPrepare { return e } -type namedValue struct { - Name string - Ordinal int - Value driver.Value -} - // Query meets http://golang.org/pkg/database/sql/driver/#Queryer func (c *sqlmock) Query(query string, args []driver.Value) (driver.Rows, error) { - namedArgs := make([]namedValue, len(args)) + namedArgs := make([]driver.NamedValue, len(args)) for i, v := range args { - namedArgs[i] = namedValue{ + namedArgs[i] = driver.NamedValue{ Ordinal: i + 1, Value: v, } @@ -428,7 +422,7 @@ func (c *sqlmock) Query(query string, args []driver.Value) (driver.Rows, error) return ex.rows, nil } -func (c *sqlmock) query(query string, args []namedValue) (*ExpectedQuery, error) { +func (c *sqlmock) query(query string, args []driver.NamedValue) (*ExpectedQuery, error) { var expected *ExpectedQuery var fulfilled int var ok bool diff --git a/sqlmock_go18.go b/sqlmock_go18.go index 8fe9c1d..1788a98 100644 --- a/sqlmock_go18.go +++ b/sqlmock_go18.go @@ -15,12 +15,7 @@ var ErrCancelled = errors.New("canceling query due to user request") // Implement the "QueryerContext" interface func (c *sqlmock) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { - namedArgs := make([]namedValue, len(args)) - for i, nv := range args { - namedArgs[i] = namedValue(nv) - } - - ex, err := c.query(query, namedArgs) + ex, err := c.query(query, args) if ex != nil { select { case <-time.After(ex.delay): @@ -38,12 +33,7 @@ func (c *sqlmock) QueryContext(ctx context.Context, query string, args []driver. // Implement the "ExecerContext" interface func (c *sqlmock) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { - namedArgs := make([]namedValue, len(args)) - for i, nv := range args { - namedArgs[i] = namedValue(nv) - } - - ex, err := c.exec(query, namedArgs) + ex, err := c.exec(query, args) if ex != nil { select { case <-time.After(ex.delay): diff --git a/sqlmock_go19.go b/sqlmock_go19.go index c0f2424..61b6da1 100644 --- a/sqlmock_go19.go +++ b/sqlmock_go19.go @@ -10,6 +10,8 @@ import ( // CheckNamedValue meets https://golang.org/pkg/database/sql/driver/#NamedValueChecker func (c *sqlmock) CheckNamedValue(nv *driver.NamedValue) (err error) { switch nv.Value.(type) { + case sql.NamedArg: + return nil case sql.Out: return nil default: From f3f5a5d16b46f0d4b4a0867c55e3ca649e448114 Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Mon, 2 Dec 2019 14:39:36 +0300 Subject: [PATCH 02/14] added support for mammoths --- expectations.go | 15 ----- expectations_before_go18.go | 17 ++++- expectations_before_go18_test.go | 95 +++++++++++++++++++++++++++ expectations_go18.go | 15 +++++ expectations_go18_test.go | 93 ++++++++++++++++++++++++++ expectations_go19_test.go | 18 +++++ expectations_test.go | 109 ------------------------------- sqlmock_before_go18.go | 74 +++++++++++++++++++++ 8 files changed, 311 insertions(+), 125 deletions(-) create mode 100644 expectations_before_go18_test.go create mode 100644 sqlmock_before_go18.go diff --git a/expectations.go b/expectations.go index b36470f..24d93ca 100644 --- a/expectations.go +++ b/expectations.go @@ -338,18 +338,3 @@ type queryBasedExpectation struct { converter driver.ValueConverter args []driver.Value } - -func (e *queryBasedExpectation) attemptArgMatch(args []driver.NamedValue) (err error) { - // catch panic - defer func() { - if e := recover(); e != nil { - _, ok := e.(error) - if !ok { - err = fmt.Errorf(e.(string)) - } - } - }() - - err = e.argsMatches(args) - return -} diff --git a/expectations_before_go18.go b/expectations_before_go18.go index 62d885b..f6e7b4e 100644 --- a/expectations_before_go18.go +++ b/expectations_before_go18.go @@ -15,7 +15,7 @@ func (e *ExpectedQuery) WillReturnRows(rows *Rows) *ExpectedQuery { return e } -func (e *queryBasedExpectation) argsMatches(args []driver.NamedValue) error { +func (e *queryBasedExpectation) argsMatches(args []namedValue) error { if nil == e.args { return nil } @@ -50,3 +50,18 @@ func (e *queryBasedExpectation) argsMatches(args []driver.NamedValue) error { } return nil } + +func (e *queryBasedExpectation) attemptArgMatch(args []namedValue) (err error) { + // catch panic + defer func() { + if e := recover(); e != nil { + _, ok := e.(error) + if !ok { + err = fmt.Errorf(e.(string)) + } + } + }() + + err = e.argsMatches(args) + return +} diff --git a/expectations_before_go18_test.go b/expectations_before_go18_test.go new file mode 100644 index 0000000..028f11f --- /dev/null +++ b/expectations_before_go18_test.go @@ -0,0 +1,95 @@ +// +build !go1.8 + +package sqlmock + +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) + } + + e.args = []driver.Value{5, "str"} + + against = []namedValue{{Value: int64(5), Ordinal: 1}} + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since the size is not the same") + } + + against = []namedValue{ + {Value: int64(3), Ordinal: 1}, + {Value: "str", Ordinal: 2}, + } + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since the first argument (int value) is different") + } + + against = []namedValue{ + {Value: int64(5), Ordinal: 1}, + {Value: "st", Ordinal: 2}, + } + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since the second argument (string value) is different") + } + + against = []namedValue{ + {Value: int64(5), Ordinal: 1}, + {Value: "str", Ordinal: 2}, + } + if err := e.argsMatches(against); err != nil { + t.Errorf("arguments should match, but it did not: %s", err) + } + + const longForm = "Jan 2, 2006 at 3:04pm (MST)" + tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)") + e.args = []driver.Value{5, tm} + + against = []namedValue{ + {Value: int64(5), Ordinal: 1}, + {Value: tm, Ordinal: 2}, + } + if err := e.argsMatches(against); err != nil { + t.Error("arguments should match, but it did not") + } + + e.args = []driver.Value{5, AnyArg()} + if err := e.argsMatches(against); err != nil { + t.Errorf("arguments should match, but it did not: %s", err) + } +} + +func TestQueryExpectationArgComparisonBool(t *testing.T) { + var e *queryBasedExpectation + + e = &queryBasedExpectation{args: []driver.Value{true}, converter: driver.DefaultParameterConverter} + against := []namedValue{ + {Value: true, Ordinal: 1}, + } + if err := e.argsMatches(against); err != nil { + t.Error("arguments should match, since arguments are the same") + } + + e = &queryBasedExpectation{args: []driver.Value{false}, converter: driver.DefaultParameterConverter} + against = []namedValue{ + {Value: false, Ordinal: 1}, + } + if err := e.argsMatches(against); err != nil { + t.Error("arguments should match, since argument are the same") + } + + e = &queryBasedExpectation{args: []driver.Value{true}, converter: driver.DefaultParameterConverter} + against = []namedValue{ + {Value: false, Ordinal: 1}, + } + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since argument is different") + } + + e = &queryBasedExpectation{args: []driver.Value{false}, converter: driver.DefaultParameterConverter} + against = []namedValue{ + {Value: true, Ordinal: 1}, + } + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since argument is different") + } +} diff --git a/expectations_go18.go b/expectations_go18.go index 8f4d23c..172bb6c 100644 --- a/expectations_go18.go +++ b/expectations_go18.go @@ -60,3 +60,18 @@ func (e *queryBasedExpectation) argsMatches(args []driver.NamedValue) error { } return nil } + +func (e *queryBasedExpectation) attemptArgMatch(args []driver.NamedValue) (err error) { + // catch panic + defer func() { + if e := recover(); e != nil { + _, ok := e.(error) + if !ok { + err = fmt.Errorf(e.(string)) + } + } + }() + + err = e.argsMatches(args) + return +} diff --git a/expectations_go18_test.go b/expectations_go18_test.go index c9bed7f..bb2f7ee 100644 --- a/expectations_go18_test.go +++ b/expectations_go18_test.go @@ -6,8 +6,101 @@ import ( "database/sql" "database/sql/driver" "testing" + "time" ) +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) + } + + e.args = []driver.Value{5, "str"} + + against = []driver.NamedValue{{Value: int64(5), Ordinal: 1}} + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since the size is not the same") + } + + against = []driver.NamedValue{ + {Value: int64(3), Ordinal: 1}, + {Value: "str", Ordinal: 2}, + } + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since the first argument (int value) is different") + } + + against = []driver.NamedValue{ + {Value: int64(5), Ordinal: 1}, + {Value: "st", Ordinal: 2}, + } + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since the second argument (string value) is different") + } + + against = []driver.NamedValue{ + {Value: int64(5), Ordinal: 1}, + {Value: "str", Ordinal: 2}, + } + if err := e.argsMatches(against); err != nil { + t.Errorf("arguments should match, but it did not: %s", err) + } + + const longForm = "Jan 2, 2006 at 3:04pm (MST)" + tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)") + e.args = []driver.Value{5, tm} + + against = []driver.NamedValue{ + {Value: int64(5), Ordinal: 1}, + {Value: tm, Ordinal: 2}, + } + if err := e.argsMatches(against); err != nil { + t.Error("arguments should match, but it did not") + } + + e.args = []driver.Value{5, AnyArg()} + if err := e.argsMatches(against); err != nil { + t.Errorf("arguments should match, but it did not: %s", err) + } +} + +func TestQueryExpectationArgComparisonBool(t *testing.T) { + var e *queryBasedExpectation + + e = &queryBasedExpectation{args: []driver.Value{true}, converter: driver.DefaultParameterConverter} + against := []driver.NamedValue{ + {Value: true, Ordinal: 1}, + } + if err := e.argsMatches(against); err != nil { + t.Error("arguments should match, since arguments are the same") + } + + e = &queryBasedExpectation{args: []driver.Value{false}, converter: driver.DefaultParameterConverter} + against = []driver.NamedValue{ + {Value: false, Ordinal: 1}, + } + if err := e.argsMatches(against); err != nil { + t.Error("arguments should match, since argument are the same") + } + + e = &queryBasedExpectation{args: []driver.Value{true}, converter: driver.DefaultParameterConverter} + against = []driver.NamedValue{ + {Value: false, Ordinal: 1}, + } + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since argument is different") + } + + e = &queryBasedExpectation{args: []driver.Value{false}, converter: driver.DefaultParameterConverter} + against = []driver.NamedValue{ + {Value: true, Ordinal: 1}, + } + if err := e.argsMatches(against); err == nil { + t.Error("arguments should not match, since argument is different") + } +} + func TestQueryExpectationNamedArgComparison(t *testing.T) { e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} against := []driver.NamedValue{{Value: int64(5), Name: "id"}} diff --git a/expectations_go19_test.go b/expectations_go19_test.go index 4ea5f04..b935796 100644 --- a/expectations_go19_test.go +++ b/expectations_go19_test.go @@ -4,9 +4,27 @@ package sqlmock import ( "context" + "database/sql/driver" + "errors" + "fmt" "testing" ) +type CustomConverter struct{} + +func (s CustomConverter) ConvertValue(v interface{}) (driver.Value, error) { + switch v.(type) { + case string: + return v.(string), nil + case []string: + return v.([]string), nil + case int: + return v.(int), nil + default: + return nil, errors.New(fmt.Sprintf("cannot convert %T with value %v", v, v)) + } +} + func TestCustomValueConverterExec(t *testing.T) { db, mock, _ := New(ValueConverterOption(CustomConverter{})) expectedQuery := "INSERT INTO tags \\(name,email,age,hobbies\\) VALUES \\(\\?,\\?,\\?,\\?\\)" diff --git a/expectations_test.go b/expectations_test.go index 7752de0..7d139ca 100644 --- a/expectations_test.go +++ b/expectations_test.go @@ -1,106 +1,11 @@ package sqlmock import ( - "database/sql/driver" - "errors" "fmt" "reflect" "testing" - "time" ) -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) - } - - e.args = []driver.Value{5, "str"} - - against = []driver.NamedValue{{Value: int64(5), Ordinal: 1}} - if err := e.argsMatches(against); err == nil { - t.Error("arguments should not match, since the size is not the same") - } - - against = []driver.NamedValue{ - {Value: int64(3), Ordinal: 1}, - {Value: "str", Ordinal: 2}, - } - if err := e.argsMatches(against); err == nil { - t.Error("arguments should not match, since the first argument (int value) is different") - } - - against = []driver.NamedValue{ - {Value: int64(5), Ordinal: 1}, - {Value: "st", Ordinal: 2}, - } - if err := e.argsMatches(against); err == nil { - t.Error("arguments should not match, since the second argument (string value) is different") - } - - against = []driver.NamedValue{ - {Value: int64(5), Ordinal: 1}, - {Value: "str", Ordinal: 2}, - } - if err := e.argsMatches(against); err != nil { - t.Errorf("arguments should match, but it did not: %s", err) - } - - const longForm = "Jan 2, 2006 at 3:04pm (MST)" - tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)") - e.args = []driver.Value{5, tm} - - against = []driver.NamedValue{ - {Value: int64(5), Ordinal: 1}, - {Value: tm, Ordinal: 2}, - } - if err := e.argsMatches(against); err != nil { - t.Error("arguments should match, but it did not") - } - - e.args = []driver.Value{5, AnyArg()} - if err := e.argsMatches(against); err != nil { - t.Errorf("arguments should match, but it did not: %s", err) - } -} - -func TestQueryExpectationArgComparisonBool(t *testing.T) { - var e *queryBasedExpectation - - e = &queryBasedExpectation{args: []driver.Value{true}, converter: driver.DefaultParameterConverter} - against := []driver.NamedValue{ - {Value: true, Ordinal: 1}, - } - if err := e.argsMatches(against); err != nil { - t.Error("arguments should match, since arguments are the same") - } - - e = &queryBasedExpectation{args: []driver.Value{false}, converter: driver.DefaultParameterConverter} - against = []driver.NamedValue{ - {Value: false, Ordinal: 1}, - } - if err := e.argsMatches(against); err != nil { - t.Error("arguments should match, since argument are the same") - } - - e = &queryBasedExpectation{args: []driver.Value{true}, converter: driver.DefaultParameterConverter} - against = []driver.NamedValue{ - {Value: false, Ordinal: 1}, - } - if err := e.argsMatches(against); err == nil { - t.Error("arguments should not match, since argument is different") - } - - e = &queryBasedExpectation{args: []driver.Value{false}, converter: driver.DefaultParameterConverter} - against = []driver.NamedValue{ - {Value: true, Ordinal: 1}, - } - if err := e.argsMatches(against); err == nil { - t.Error("arguments should not match, since argument is different") - } -} - func ExampleExpectedExec() { db, mock, _ := New() result := NewErrorResult(fmt.Errorf("some error")) @@ -140,20 +45,6 @@ func TestBuildQuery(t *testing.T) { } } -type CustomConverter struct{} - -func (s CustomConverter) ConvertValue(v interface{}) (driver.Value, error) { - switch v.(type) { - case string: - return v.(string), nil - case []string: - return v.([]string), nil - case int: - return v.(int), nil - default: - return nil, errors.New(fmt.Sprintf("cannot convert %T with value %v", v, v)) - } -} func TestCustomValueConverterQueryScan(t *testing.T) { db, mock, _ := New(ValueConverterOption(CustomConverter{})) query := ` diff --git a/sqlmock_before_go18.go b/sqlmock_before_go18.go new file mode 100644 index 0000000..b78663d --- /dev/null +++ b/sqlmock_before_go18.go @@ -0,0 +1,74 @@ +// +build !go1.8 + +/* +Package sqlmock is a mock library implementing sql driver. Which has one and only +purpose - to simulate any sql driver behavior in tests, without needing a real +database connection. It helps to maintain correct **TDD** workflow. + +It does not require any modifications to your source code in order to test +and mock database operations. Supports concurrency and multiple database mocking. + +The driver allows to mock any sql driver method behavior. +*/ +package sqlmock + +func (c *sqlmock) exec(query string, args []namedValue) (*ExpectedExec, error) { + var expected *ExpectedExec + var fulfilled int + var ok bool + for _, next := range c.expected { + next.Lock() + if next.fulfilled() { + next.Unlock() + fulfilled++ + continue + } + + if c.ordered { + if expected, ok = next.(*ExpectedExec); ok { + break + } + next.Unlock() + return nil, fmt.Errorf("call to ExecQuery '%s' with args %+v, was not expected, next expectation is: %s", query, args, next) + } + if exec, ok := next.(*ExpectedExec); ok { + if err := c.queryMatcher.Match(exec.expectSQL, query); err != nil { + next.Unlock() + continue + } + + if err := exec.attemptArgMatch(args); err == nil { + expected = exec + break + } + } + next.Unlock() + } + if expected == nil { + msg := "call to ExecQuery '%s' with args %+v was not expected" + if fulfilled == len(c.expected) { + msg = "all expectations were already fulfilled, " + msg + } + return nil, fmt.Errorf(msg, query, args) + } + defer expected.Unlock() + + if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil { + return nil, fmt.Errorf("ExecQuery: %v", err) + } + + if err := expected.argsMatches(args); err != nil { + return nil, fmt.Errorf("ExecQuery '%s', arguments do not match: %s", query, err) + } + + expected.triggered = true + if expected.err != nil { + return expected, expected.err // mocked to return error + } + + if expected.result == nil { + return nil, fmt.Errorf("ExecQuery '%s' with args %+v, must return a database/sql/driver.Result, but it was not set for expectation %T as %+v", query, args, expected, expected) + } + + return expected, nil +} From db5c27220825ebc34847b6dc48add632fca385c6 Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Mon, 2 Dec 2019 14:48:50 +0300 Subject: [PATCH 03/14] fix --- expectations.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/expectations.go b/expectations.go index ae2a47f..5c82c7b 100644 --- a/expectations.go +++ b/expectations.go @@ -339,21 +339,6 @@ type queryBasedExpectation struct { args []driver.Value } -func (e *queryBasedExpectation) attemptArgMatch(args []namedValue) (err error) { - // catch panic - defer func() { - if e := recover(); e != nil { - _, ok := e.(error) - if !ok { - err = fmt.Errorf(e.(string)) - } - } - }() - - err = e.argsMatches(args) - return -} - // ExpectedPing is used to manage *sql.DB.Ping expectations. // Returned by *Sqlmock.ExpectPing. type ExpectedPing struct { From 118815aba34b02ceb49b8755e0b40a2d4268b765 Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Mon, 2 Dec 2019 14:54:33 +0300 Subject: [PATCH 04/14] fix --- expectations_go19_test.go | 18 ------------------ expectations_test.go | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/expectations_go19_test.go b/expectations_go19_test.go index b935796..4ea5f04 100644 --- a/expectations_go19_test.go +++ b/expectations_go19_test.go @@ -4,27 +4,9 @@ package sqlmock import ( "context" - "database/sql/driver" - "errors" - "fmt" "testing" ) -type CustomConverter struct{} - -func (s CustomConverter) ConvertValue(v interface{}) (driver.Value, error) { - switch v.(type) { - case string: - return v.(string), nil - case []string: - return v.([]string), nil - case int: - return v.(int), nil - default: - return nil, errors.New(fmt.Sprintf("cannot convert %T with value %v", v, v)) - } -} - func TestCustomValueConverterExec(t *testing.T) { db, mock, _ := New(ValueConverterOption(CustomConverter{})) expectedQuery := "INSERT INTO tags \\(name,email,age,hobbies\\) VALUES \\(\\?,\\?,\\?,\\?\\)" diff --git a/expectations_test.go b/expectations_test.go index 7d139ca..afda582 100644 --- a/expectations_test.go +++ b/expectations_test.go @@ -1,11 +1,28 @@ package sqlmock import ( + "database/sql/driver" + "errors" "fmt" "reflect" "testing" ) +type CustomConverter struct{} + +func (s CustomConverter) ConvertValue(v interface{}) (driver.Value, error) { + switch v.(type) { + case string: + return v.(string), nil + case []string: + return v.([]string), nil + case int: + return v.(int), nil + default: + return nil, errors.New(fmt.Sprintf("cannot convert %T with value %v", v, v)) + } +} + func ExampleExpectedExec() { db, mock, _ := New() result := NewErrorResult(fmt.Errorf("some error")) From 7c97a0ddd108fcb218d3b3c5615c407e1e42b29a Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Tue, 3 Dec 2019 07:35:47 +0300 Subject: [PATCH 05/14] fix go version less 1.8 --- sqlmock.go | 164 ----------------------------------------- sqlmock_before_go18.go | 109 +++++++++++++++++++++++++++ sqlmock_go18.go | 164 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+), 164 deletions(-) diff --git a/sqlmock.go b/sqlmock.go index bacc750..90f789b 100644 --- a/sqlmock.go +++ b/sqlmock.go @@ -265,88 +265,6 @@ func (c *sqlmock) ExpectBegin() *ExpectedBegin { return e } -// Exec meets http://golang.org/pkg/database/sql/driver/#Execer -func (c *sqlmock) Exec(query string, args []driver.Value) (driver.Result, error) { - namedArgs := make([]driver.NamedValue, len(args)) - for i, v := range args { - namedArgs[i] = driver.NamedValue{ - Ordinal: i + 1, - Value: v, - } - } - - ex, err := c.exec(query, namedArgs) - if ex != nil { - time.Sleep(ex.delay) - } - if err != nil { - return nil, err - } - - return ex.result, nil -} - -func (c *sqlmock) exec(query string, args []driver.NamedValue) (*ExpectedExec, error) { - var expected *ExpectedExec - var fulfilled int - var ok bool - for _, next := range c.expected { - next.Lock() - if next.fulfilled() { - next.Unlock() - fulfilled++ - continue - } - - if c.ordered { - if expected, ok = next.(*ExpectedExec); ok { - break - } - next.Unlock() - return nil, fmt.Errorf("call to ExecQuery '%s' with args %+v, was not expected, next expectation is: %s", query, args, next) - } - if exec, ok := next.(*ExpectedExec); ok { - if err := c.queryMatcher.Match(exec.expectSQL, query); err != nil { - next.Unlock() - continue - } - - if err := exec.attemptArgMatch(args); err == nil { - expected = exec - break - } - } - next.Unlock() - } - if expected == nil { - msg := "call to ExecQuery '%s' with args %+v was not expected" - if fulfilled == len(c.expected) { - msg = "all expectations were already fulfilled, " + msg - } - return nil, fmt.Errorf(msg, query, args) - } - defer expected.Unlock() - - if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil { - return nil, fmt.Errorf("ExecQuery: %v", err) - } - - if err := expected.argsMatches(args); err != nil { - return nil, fmt.Errorf("ExecQuery '%s', arguments do not match: %s", query, err) - } - - expected.triggered = true - if expected.err != nil { - return expected, expected.err // mocked to return error - } - - if expected.result == nil { - return nil, fmt.Errorf("ExecQuery '%s' with args %+v, must return a database/sql/driver.Result, but it was not set for expectation %T as %+v", query, args, expected, expected) - } - - return expected, nil -} - func (c *sqlmock) ExpectExec(expectedSQL string) *ExpectedExec { e := &ExpectedExec{} e.expectSQL = expectedSQL @@ -421,88 +339,6 @@ func (c *sqlmock) ExpectPrepare(expectedSQL string) *ExpectedPrepare { return e } -// Query meets http://golang.org/pkg/database/sql/driver/#Queryer -func (c *sqlmock) Query(query string, args []driver.Value) (driver.Rows, error) { - namedArgs := make([]driver.NamedValue, len(args)) - for i, v := range args { - namedArgs[i] = driver.NamedValue{ - Ordinal: i + 1, - Value: v, - } - } - - ex, err := c.query(query, namedArgs) - if ex != nil { - time.Sleep(ex.delay) - } - if err != nil { - return nil, err - } - - return ex.rows, nil -} - -func (c *sqlmock) query(query string, args []driver.NamedValue) (*ExpectedQuery, error) { - var expected *ExpectedQuery - var fulfilled int - var ok bool - for _, next := range c.expected { - next.Lock() - if next.fulfilled() { - next.Unlock() - fulfilled++ - continue - } - - if c.ordered { - if expected, ok = next.(*ExpectedQuery); ok { - break - } - next.Unlock() - return nil, fmt.Errorf("call to Query '%s' with args %+v, was not expected, next expectation is: %s", query, args, next) - } - if qr, ok := next.(*ExpectedQuery); ok { - if err := c.queryMatcher.Match(qr.expectSQL, query); err != nil { - next.Unlock() - continue - } - if err := qr.attemptArgMatch(args); err == nil { - expected = qr - break - } - } - next.Unlock() - } - - if expected == nil { - msg := "call to Query '%s' with args %+v was not expected" - if fulfilled == len(c.expected) { - msg = "all expectations were already fulfilled, " + msg - } - return nil, fmt.Errorf(msg, query, args) - } - - defer expected.Unlock() - - if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil { - return nil, fmt.Errorf("Query: %v", err) - } - - if err := expected.argsMatches(args); err != nil { - return nil, fmt.Errorf("Query '%s', arguments do not match: %s", query, err) - } - - expected.triggered = true - if expected.err != nil { - return expected, expected.err // mocked to return error - } - - if expected.rows == nil { - return nil, fmt.Errorf("Query '%s' with args %+v, must return a database/sql/driver.Rows, but it was not set for expectation %T as %+v", query, args, expected, expected) - } - return expected, nil -} - func (c *sqlmock) ExpectQuery(expectedSQL string) *ExpectedQuery { e := &ExpectedQuery{} e.expectSQL = expectedSQL diff --git a/sqlmock_before_go18.go b/sqlmock_before_go18.go index 36ec915..35433b5 100644 --- a/sqlmock_before_go18.go +++ b/sqlmock_before_go18.go @@ -4,11 +4,120 @@ package sqlmock import "log" +type namedValue struct { + Name string + Ordinal int + Value driver.Value +} + func (c *sqlmock) ExpectPing() *ExpectedPing { log.Println("ExpectPing has no effect on Go 1.7 or below") return &ExpectedPing{} } +// Query meets http://golang.org/pkg/database/sql/driver/#Queryer +func (c *sqlmock) Query(query string, args []driver.Value) (driver.Rows, error) { + namedArgs := make([]namedValue, len(args)) + for i, v := range args { + namedArgs[i] = namedValue{ + Ordinal: i + 1, + Value: v, + } + } + + ex, err := c.query(query, namedArgs) + if ex != nil { + time.Sleep(ex.delay) + } + if err != nil { + return nil, err + } + + return ex.rows, nil +} + +func (c *sqlmock) query(query string, args []namedValue) (*ExpectedQuery, error) { + var expected *ExpectedQuery + var fulfilled int + var ok bool + for _, next := range c.expected { + next.Lock() + if next.fulfilled() { + next.Unlock() + fulfilled++ + continue + } + + if c.ordered { + if expected, ok = next.(*ExpectedQuery); ok { + break + } + next.Unlock() + return nil, fmt.Errorf("call to Query '%s' with args %+v, was not expected, next expectation is: %s", query, args, next) + } + if qr, ok := next.(*ExpectedQuery); ok { + if err := c.queryMatcher.Match(qr.expectSQL, query); err != nil { + next.Unlock() + continue + } + if err := qr.attemptArgMatch(args); err == nil { + expected = qr + break + } + } + next.Unlock() + } + + if expected == nil { + msg := "call to Query '%s' with args %+v was not expected" + if fulfilled == len(c.expected) { + msg = "all expectations were already fulfilled, " + msg + } + return nil, fmt.Errorf(msg, query, args) + } + + defer expected.Unlock() + + if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil { + return nil, fmt.Errorf("Query: %v", err) + } + + if err := expected.argsMatches(args); err != nil { + return nil, fmt.Errorf("Query '%s', arguments do not match: %s", query, err) + } + + expected.triggered = true + if expected.err != nil { + return expected, expected.err // mocked to return error + } + + if expected.rows == nil { + return nil, fmt.Errorf("Query '%s' with args %+v, must return a database/sql/driver.Rows, but it was not set for expectation %T as %+v", query, args, expected, expected) + } + return expected, nil +} + +// Exec meets http://golang.org/pkg/database/sql/driver/#Execer +func (c *sqlmock) Exec(query string, args []driver.Value) (driver.Result, error) { + namedArgs := make([]namedValue, len(args)) + for i, v := range args { + namedArgs[i] = namedValue{ + Ordinal: i + 1, + Value: v, + } + } + + ex, err := c.exec(query, namedArgs) + if ex != nil { + time.Sleep(ex.delay) + } + if err != nil { + return nil, err + } + + return ex.result, nil +} + func (c *sqlmock) exec(query string, args []namedValue) (*ExpectedExec, error) { var expected *ExpectedExec var fulfilled int diff --git a/sqlmock_go18.go b/sqlmock_go18.go index 00708d3..0fee0dd 100644 --- a/sqlmock_go18.go +++ b/sqlmock_go18.go @@ -160,4 +160,168 @@ func (c *sqlmock) ExpectPing() *ExpectedPing { return e } +// Query meets http://golang.org/pkg/database/sql/driver/#Queryer +func (c *sqlmock) Query(query string, args []driver.Value) (driver.Rows, error) { + namedArgs := make([]driver.NamedValue, len(args)) + for i, v := range args { + namedArgs[i] = driver.NamedValue{ + Ordinal: i + 1, + Value: v, + } + } + + ex, err := c.query(query, namedArgs) + if ex != nil { + time.Sleep(ex.delay) + } + if err != nil { + return nil, err + } + + return ex.rows, nil +} + +func (c *sqlmock) query(query string, args []driver.NamedValue) (*ExpectedQuery, error) { + var expected *ExpectedQuery + var fulfilled int + var ok bool + for _, next := range c.expected { + next.Lock() + if next.fulfilled() { + next.Unlock() + fulfilled++ + continue + } + + if c.ordered { + if expected, ok = next.(*ExpectedQuery); ok { + break + } + next.Unlock() + return nil, fmt.Errorf("call to Query '%s' with args %+v, was not expected, next expectation is: %s", query, args, next) + } + if qr, ok := next.(*ExpectedQuery); ok { + if err := c.queryMatcher.Match(qr.expectSQL, query); err != nil { + next.Unlock() + continue + } + if err := qr.attemptArgMatch(args); err == nil { + expected = qr + break + } + } + next.Unlock() + } + + if expected == nil { + msg := "call to Query '%s' with args %+v was not expected" + if fulfilled == len(c.expected) { + msg = "all expectations were already fulfilled, " + msg + } + return nil, fmt.Errorf(msg, query, args) + } + + defer expected.Unlock() + + if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil { + return nil, fmt.Errorf("Query: %v", err) + } + + if err := expected.argsMatches(args); err != nil { + return nil, fmt.Errorf("Query '%s', arguments do not match: %s", query, err) + } + + expected.triggered = true + if expected.err != nil { + return expected, expected.err // mocked to return error + } + + if expected.rows == nil { + return nil, fmt.Errorf("Query '%s' with args %+v, must return a database/sql/driver.Rows, but it was not set for expectation %T as %+v", query, args, expected, expected) + } + return expected, nil +} + +// Exec meets http://golang.org/pkg/database/sql/driver/#Execer +func (c *sqlmock) Exec(query string, args []driver.Value) (driver.Result, error) { + namedArgs := make([]driver.NamedValue, len(args)) + for i, v := range args { + namedArgs[i] = driver.NamedValue{ + Ordinal: i + 1, + Value: v, + } + } + + ex, err := c.exec(query, namedArgs) + if ex != nil { + time.Sleep(ex.delay) + } + if err != nil { + return nil, err + } + + return ex.result, nil +} + +func (c *sqlmock) exec(query string, args []driver.NamedValue) (*ExpectedExec, error) { + var expected *ExpectedExec + var fulfilled int + var ok bool + for _, next := range c.expected { + next.Lock() + if next.fulfilled() { + next.Unlock() + fulfilled++ + continue + } + + if c.ordered { + if expected, ok = next.(*ExpectedExec); ok { + break + } + next.Unlock() + return nil, fmt.Errorf("call to ExecQuery '%s' with args %+v, was not expected, next expectation is: %s", query, args, next) + } + if exec, ok := next.(*ExpectedExec); ok { + if err := c.queryMatcher.Match(exec.expectSQL, query); err != nil { + next.Unlock() + continue + } + + if err := exec.attemptArgMatch(args); err == nil { + expected = exec + break + } + } + next.Unlock() + } + if expected == nil { + msg := "call to ExecQuery '%s' with args %+v was not expected" + if fulfilled == len(c.expected) { + msg = "all expectations were already fulfilled, " + msg + } + return nil, fmt.Errorf(msg, query, args) + } + defer expected.Unlock() + + if err := c.queryMatcher.Match(expected.expectSQL, query); err != nil { + return nil, fmt.Errorf("ExecQuery: %v", err) + } + + if err := expected.argsMatches(args); err != nil { + return nil, fmt.Errorf("ExecQuery '%s', arguments do not match: %s", query, err) + } + + expected.triggered = true + if expected.err != nil { + return expected, expected.err // mocked to return error + } + + if expected.result == nil { + return nil, fmt.Errorf("ExecQuery '%s' with args %+v, must return a database/sql/driver.Result, but it was not set for expectation %T as %+v", query, args, expected, expected) + } + + return expected, nil +} + // @TODO maybe add ExpectedBegin.WithOptions(driver.TxOptions) From 64c12dcb17ed4a6468bc25437860ce6b283bc343 Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Tue, 3 Dec 2019 07:40:19 +0300 Subject: [PATCH 06/14] fix import driver --- sqlmock_before_go18.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sqlmock_before_go18.go b/sqlmock_before_go18.go index 35433b5..4b01545 100644 --- a/sqlmock_before_go18.go +++ b/sqlmock_before_go18.go @@ -2,7 +2,10 @@ package sqlmock -import "log" +import ( + "database/sql/driver" + "log" +) type namedValue struct { Name string From 27bffac965be7699b6151e0ebb3b3d8ccdee2d0c Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Tue, 3 Dec 2019 07:42:48 +0300 Subject: [PATCH 07/14] fix import time and fmt --- sqlmock_before_go18.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sqlmock_before_go18.go b/sqlmock_before_go18.go index 4b01545..1a5b63a 100644 --- a/sqlmock_before_go18.go +++ b/sqlmock_before_go18.go @@ -4,7 +4,9 @@ package sqlmock import ( "database/sql/driver" + "fmt" "log" + "time" ) type namedValue struct { From e062dfc20223e6e030d86a45c9c97c366c2a921f Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Tue, 3 Dec 2019 07:45:23 +0300 Subject: [PATCH 08/14] fix import for test --- expectations_before_go18_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/expectations_before_go18_test.go b/expectations_before_go18_test.go index 028f11f..deee8f9 100644 --- a/expectations_before_go18_test.go +++ b/expectations_before_go18_test.go @@ -2,6 +2,12 @@ package sqlmock +import ( + "database/sql/driver" + "testing" + "time" +) + func TestQueryExpectationArgComparison(t *testing.T) { e := &queryBasedExpectation{converter: driver.DefaultParameterConverter} against := []namedValue{{Value: int64(5), Ordinal: 1}} From 5a7ddb9845c0bb860a847ff38449c441600bdc98 Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Tue, 3 Dec 2019 12:47:50 +0300 Subject: [PATCH 09/14] added tests --- sqlmock_go18.go | 2 ++ sqlmock_go19.go | 2 -- sqlmock_go19_test.go | 31 +++++++++++++++++++++++++++++ sqlmock_test.go | 43 ++++++++++++++++++++++++++++++++++++++++ statement.go | 12 ----------- statement_before_go18.go | 17 ++++++++++++++++ statement_go18.go | 26 ++++++++++++++++++++++++ 7 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 statement_before_go18.go create mode 100644 statement_go18.go diff --git a/sqlmock_go18.go b/sqlmock_go18.go index 0fee0dd..dc37b18 100644 --- a/sqlmock_go18.go +++ b/sqlmock_go18.go @@ -161,6 +161,7 @@ func (c *sqlmock) ExpectPing() *ExpectedPing { } // Query meets http://golang.org/pkg/database/sql/driver/#Queryer +// Deprecated: Drivers should implement QueryerContext instead. func (c *sqlmock) Query(query string, args []driver.Value) (driver.Rows, error) { namedArgs := make([]driver.NamedValue, len(args)) for i, v := range args { @@ -243,6 +244,7 @@ func (c *sqlmock) query(query string, args []driver.NamedValue) (*ExpectedQuery, } // Exec meets http://golang.org/pkg/database/sql/driver/#Execer +// Deprecated: Drivers should implement ExecerContext instead. func (c *sqlmock) Exec(query string, args []driver.Value) (driver.Result, error) { namedArgs := make([]driver.NamedValue, len(args)) for i, v := range args { diff --git a/sqlmock_go19.go b/sqlmock_go19.go index 61b6da1..c0f2424 100644 --- a/sqlmock_go19.go +++ b/sqlmock_go19.go @@ -10,8 +10,6 @@ import ( // CheckNamedValue meets https://golang.org/pkg/database/sql/driver/#NamedValueChecker func (c *sqlmock) CheckNamedValue(nv *driver.NamedValue) (err error) { switch nv.Value.(type) { - case sql.NamedArg: - return nil case sql.Out: return nil default: diff --git a/sqlmock_go19_test.go b/sqlmock_go19_test.go index 6c69559..910d704 100644 --- a/sqlmock_go19_test.go +++ b/sqlmock_go19_test.go @@ -3,6 +3,8 @@ package sqlmock import ( + "database/sql" + "database/sql/driver" "errors" "testing" ) @@ -37,3 +39,32 @@ func TestStatementTX(t *testing.T) { t.Fatalf("unexpected result: %v", err) } } + +func Test_sqlmock_CheckNamedValue(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() + tests := []struct { + name string + arg *driver.NamedValue + wantErr bool + }{ + { + arg: &driver.NamedValue{Name: "test", Value: "test"}, + wantErr: false, + }, + { + arg: &driver.NamedValue{Name: "test", Value: sql.Out{}}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := mock.(*sqlmock).CheckNamedValue(tt.arg); (err != nil) != tt.wantErr { + t.Errorf("CheckNamedValue() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/sqlmock_test.go b/sqlmock_test.go index 522ea42..7af77a2 100644 --- a/sqlmock_test.go +++ b/sqlmock_test.go @@ -2,8 +2,10 @@ package sqlmock import ( "database/sql" + "database/sql/driver" "errors" "fmt" + "reflect" "strconv" "sync" "testing" @@ -1217,3 +1219,44 @@ func queryWithTimeout(t time.Duration, db *sql.DB, query string, args ...interfa return nil, fmt.Errorf("query timed out after %v", t) } } + +func Test_sqlmock_Prepare_and_Exec(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() + query := "SELECT name, email FROM users WHERE name = ?" + + mock.ExpectPrepare("SELECT (.+) FROM users WHERE (.+)") + expected := NewResult(1, 1) + 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 (.+)").WillReturnRows(expectedRows) + + got, err := mock.(*sqlmock).Prepare(query) + if err != nil { + t.Error(err) + return + } + if got == nil { + t.Error("Prepare () stmt must not be nil") + return + } + result, err := got.Exec([]driver.Value{"test"}) + if err != nil { + t.Error(err) + return + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Eesults not equal. Expected: %v, Actual: %v", expected, result) + return + } + rows, err := got.Query([]driver.Value{"test"}) + if err != nil { + t.Error(err) + return + } + defer rows.Close() +} diff --git a/statement.go b/statement.go index 570efd9..852b8f3 100644 --- a/statement.go +++ b/statement.go @@ -1,9 +1,5 @@ package sqlmock -import ( - "database/sql/driver" -) - type statement struct { conn *sqlmock ex *ExpectedPrepare @@ -18,11 +14,3 @@ func (stmt *statement) Close() error { func (stmt *statement) NumInput() int { return -1 } - -func (stmt *statement) Exec(args []driver.Value) (driver.Result, error) { - return stmt.conn.Exec(stmt.query, args) -} - -func (stmt *statement) Query(args []driver.Value) (driver.Rows, error) { - return stmt.conn.Query(stmt.query, args) -} diff --git a/statement_before_go18.go b/statement_before_go18.go new file mode 100644 index 0000000..e2cac2b --- /dev/null +++ b/statement_before_go18.go @@ -0,0 +1,17 @@ +// +build !go1.8 + +package sqlmock + +import ( + "database/sql/driver" +) + +// Deprecated: Drivers should implement ExecerContext instead. +func (stmt *statement) Exec(args []driver.Value) (driver.Result, error) { + return stmt.conn.Exec(stmt.query, args) +} + +// Deprecated: Drivers should implement StmtQueryContext instead (or additionally). +func (stmt *statement) Query(args []driver.Value) (driver.Rows, error) { + return stmt.conn.Query(stmt.query, args) +} diff --git a/statement_go18.go b/statement_go18.go new file mode 100644 index 0000000..e083051 --- /dev/null +++ b/statement_go18.go @@ -0,0 +1,26 @@ +// +build go1.8 + +package sqlmock + +import ( + "context" + "database/sql/driver" +) + +// Deprecated: Drivers should implement ExecerContext instead. +func (stmt *statement) Exec(args []driver.Value) (driver.Result, error) { + return stmt.conn.ExecContext(context.Background(), stmt.query, convertValueToNamedValue(args)) +} + +// Deprecated: Drivers should implement StmtQueryContext instead (or additionally). +func (stmt *statement) Query(args []driver.Value) (driver.Rows, error) { + return stmt.conn.QueryContext(context.Background(), stmt.query, convertValueToNamedValue(args)) +} + +func convertValueToNamedValue(args []driver.Value) []driver.NamedValue { + namedArgs := make([]driver.NamedValue, len(args)) + for i, v := range args { + namedArgs[i] = driver.NamedValue{Ordinal: i + 1, Value: v} + } + return namedArgs +} From 5dc976bc43ce0fb61497ed7129392acf36dd61fb Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Tue, 3 Dec 2019 13:46:20 +0300 Subject: [PATCH 10/14] added tests --- sqlmock_go18_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ sqlmock_test.go | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/sqlmock_go18_test.go b/sqlmock_go18_test.go index 223e076..6080afa 100644 --- a/sqlmock_go18_test.go +++ b/sqlmock_go18_test.go @@ -5,7 +5,9 @@ package sqlmock import ( "context" "database/sql" + "database/sql/driver" "errors" + "reflect" "testing" "time" ) @@ -639,3 +641,43 @@ func TestPingExpectationsContextTimeout(t *testing.T) { t.Errorf("expected Ping to return after context timeout, but it did not in a timely fashion") } } + +func Test_sqlmock_Exec(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() + query := "SELECT name, email FROM users WHERE name = ?" + + expected := NewResult(1, 1) + mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)"). + WillReturnResult(expected) + + result, err := mock.(*sqlmock).Exec(query, []driver.Value{"test"}) + if err != nil { + t.Error(err) + return + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Results are not equal. Expected: %v, Actual: %v", expected, result) + return + } +} + +func Test_sqlmock_Query(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) + query := "SELECT name, email FROM users WHERE name = ?" + rows, err := mock.(*sqlmock).Query(query, []driver.Value{"test"}) + if err != nil { + t.Error(err) + return + } + defer rows.Close() +} diff --git a/sqlmock_test.go b/sqlmock_test.go index 7af77a2..97775df 100644 --- a/sqlmock_test.go +++ b/sqlmock_test.go @@ -1250,7 +1250,7 @@ func Test_sqlmock_Prepare_and_Exec(t *testing.T) { return } if !reflect.DeepEqual(result, expected) { - t.Errorf("Eesults not equal. Expected: %v, Actual: %v", expected, result) + t.Errorf("Results are not equal. Expected: %v, Actual: %v", expected, result) return } rows, err := got.Query([]driver.Value{"test"}) From 594a047e2fac9788c6c9bde7541f5f5b42120157 Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Wed, 4 Dec 2019 09:02:10 +0300 Subject: [PATCH 11/14] added tests --- expectations_before_go18_test.go | 17 +++++++++++++++++ expectations_go18_test.go | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/expectations_before_go18_test.go b/expectations_before_go18_test.go index deee8f9..897ebff 100644 --- a/expectations_before_go18_test.go +++ b/expectations_before_go18_test.go @@ -99,3 +99,20 @@ func TestQueryExpectationArgComparisonBool(t *testing.T) { t.Error("arguments should not match, since argument is different") } } + +type panicConverter struct { +} + +func (s panicConverter) ConvertValue(v interface{}) (driver.Value, error) { + panic(v) +} + +func Test_queryBasedExpectation_attemptArgMatch(t *testing.T) { + e := &queryBasedExpectation{converter: new(panicConverter), args: []driver.Value{"test"}} + values := []namedValue{ + {Ordinal: 1, Name: "test", Value: "test"}, + } + if err := e.attemptArgMatch(values); err == nil { + t.Errorf("error expected") + } +} diff --git a/expectations_go18_test.go b/expectations_go18_test.go index bb2f7ee..1974721 100644 --- a/expectations_go18_test.go +++ b/expectations_go18_test.go @@ -155,3 +155,20 @@ func TestQueryExpectationNamedArgComparison(t *testing.T) { t.Errorf("arguments should have matched, but it did not: %v", err) } } + +type panicConverter struct { +} + +func (s panicConverter) ConvertValue(v interface{}) (driver.Value, error) { + panic(v) +} + +func Test_queryBasedExpectation_attemptArgMatch(t *testing.T) { + e := &queryBasedExpectation{converter: new(panicConverter), args: []driver.Value{"test"}} + values := []driver.NamedValue{ + {Ordinal: 1, Name: "test", Value: "test"}, + } + if err := e.attemptArgMatch(values); err == nil { + t.Errorf("error expected") + } +} From 03d1707c76fc8ce96952c3e2f4d5f7e99bde6fc2 Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Wed, 4 Dec 2019 09:26:45 +0300 Subject: [PATCH 12/14] added tests --- sqlmock_go18_test.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/sqlmock_go18_test.go b/sqlmock_go18_test.go index 6080afa..6213266 100644 --- a/sqlmock_go18_test.go +++ b/sqlmock_go18_test.go @@ -642,6 +642,12 @@ func TestPingExpectationsContextTimeout(t *testing.T) { } } +type failArgument struct{} + +func (f failArgument) Match(_ driver.Value) bool { + return false +} + func Test_sqlmock_Exec(t *testing.T) { db, mock, err := New() if err != nil { @@ -652,7 +658,12 @@ func Test_sqlmock_Exec(t *testing.T) { expected := NewResult(1, 1) mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)"). - WillReturnResult(expected) + WillReturnResult(expected). + WithArgs("test") + + mock.ExpectExec("SELECT (.+) FROM animals WHERE (.+)"). + WillReturnError(errors.New("matcher %T could not match %d argument %T - %+v")). + WithArgs(failArgument{}) result, err := mock.(*sqlmock).Exec(query, []driver.Value{"test"}) if err != nil { @@ -663,6 +674,12 @@ func Test_sqlmock_Exec(t *testing.T) { t.Errorf("Results are not equal. Expected: %v, Actual: %v", expected, result) return } + + _, err = mock.(*sqlmock).Exec(query, []driver.Value{failArgument{}}) + if err == nil { + t.Errorf("error expected") + return + } } func Test_sqlmock_Query(t *testing.T) { From 8cf05c23ad1b9031ec364d628ffb09a11caa2f31 Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Wed, 4 Dec 2019 10:38:13 +0300 Subject: [PATCH 13/14] added tests --- sqlmock_go18_test.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/sqlmock_go18_test.go b/sqlmock_go18_test.go index 6213266..395c9f9 100644 --- a/sqlmock_go18_test.go +++ b/sqlmock_go18_test.go @@ -654,17 +654,28 @@ func Test_sqlmock_Exec(t *testing.T) { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() - query := "SELECT name, email FROM users WHERE name = ?" + + mock.ExpectBegin() + _, err = mock.(*sqlmock).Exec("", []driver.Value{}) + if err == nil { + t.Errorf("error expected") + return + } expected := NewResult(1, 1) mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)"). WillReturnResult(expected). WithArgs("test") + matchErr := errors.New("matcher sqlmock.failArgument could not match 0 argument driver.NamedValue - {Name: Ordinal:1 Value:{}}") mock.ExpectExec("SELECT (.+) FROM animals WHERE (.+)"). - WillReturnError(errors.New("matcher %T could not match %d argument %T - %+v")). + WillReturnError(matchErr). WithArgs(failArgument{}) + mock.ExpectExec("").WithArgs(failArgument{}) + + mock.(*sqlmock).expected = mock.(*sqlmock).expected[1:] + query := "SELECT name, email FROM users WHERE name = ?" result, err := mock.(*sqlmock).Exec(query, []driver.Value{"test"}) if err != nil { t.Error(err) @@ -675,7 +686,14 @@ func Test_sqlmock_Exec(t *testing.T) { return } - _, err = mock.(*sqlmock).Exec(query, []driver.Value{failArgument{}}) + failQuery := "SELECT name, sex FROM animals WHERE sex = ?" + _, err = mock.(*sqlmock).Exec(failQuery, []driver.Value{failArgument{}}) + if err == nil { + t.Errorf("error expected") + return + } + mock.(*sqlmock).ordered = false + _, err = mock.(*sqlmock).Exec("", []driver.Value{failArgument{}}) if err == nil { t.Errorf("error expected") return @@ -697,4 +715,9 @@ func Test_sqlmock_Query(t *testing.T) { return } defer rows.Close() + _, err = mock.(*sqlmock).Query(query, []driver.Value{failArgument{}}) + if err == nil { + t.Errorf("error expected") + return + } } From 4fbf60585464a79b67c3a8c6c21e57927cf444be Mon Sep 17 00:00:00 2001 From: Nikita Koryabkin Date: Wed, 4 Dec 2019 10:44:50 +0300 Subject: [PATCH 14/14] move tests --- sqlmock_go18_test.go | 82 -------------------------------------------- sqlmock_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 82 deletions(-) diff --git a/sqlmock_go18_test.go b/sqlmock_go18_test.go index 395c9f9..223e076 100644 --- a/sqlmock_go18_test.go +++ b/sqlmock_go18_test.go @@ -5,9 +5,7 @@ package sqlmock import ( "context" "database/sql" - "database/sql/driver" "errors" - "reflect" "testing" "time" ) @@ -641,83 +639,3 @@ func TestPingExpectationsContextTimeout(t *testing.T) { t.Errorf("expected Ping to return after context timeout, but it did not in a timely fashion") } } - -type failArgument struct{} - -func (f failArgument) Match(_ driver.Value) bool { - return false -} - -func Test_sqlmock_Exec(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() - - mock.ExpectBegin() - _, err = mock.(*sqlmock).Exec("", []driver.Value{}) - if err == nil { - t.Errorf("error expected") - return - } - - expected := NewResult(1, 1) - mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)"). - WillReturnResult(expected). - WithArgs("test") - - matchErr := errors.New("matcher sqlmock.failArgument could not match 0 argument driver.NamedValue - {Name: Ordinal:1 Value:{}}") - mock.ExpectExec("SELECT (.+) FROM animals WHERE (.+)"). - WillReturnError(matchErr). - WithArgs(failArgument{}) - - mock.ExpectExec("").WithArgs(failArgument{}) - - mock.(*sqlmock).expected = mock.(*sqlmock).expected[1:] - query := "SELECT name, email FROM users WHERE name = ?" - result, err := mock.(*sqlmock).Exec(query, []driver.Value{"test"}) - if err != nil { - t.Error(err) - return - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Results are not equal. Expected: %v, Actual: %v", expected, result) - return - } - - failQuery := "SELECT name, sex FROM animals WHERE sex = ?" - _, err = mock.(*sqlmock).Exec(failQuery, []driver.Value{failArgument{}}) - if err == nil { - t.Errorf("error expected") - return - } - mock.(*sqlmock).ordered = false - _, err = mock.(*sqlmock).Exec("", []driver.Value{failArgument{}}) - if err == nil { - t.Errorf("error expected") - return - } -} - -func Test_sqlmock_Query(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) - query := "SELECT name, email FROM users WHERE name = ?" - rows, err := mock.(*sqlmock).Query(query, []driver.Value{"test"}) - if err != nil { - t.Error(err) - return - } - defer rows.Close() - _, err = mock.(*sqlmock).Query(query, []driver.Value{failArgument{}}) - if err == nil { - t.Errorf("error expected") - return - } -} diff --git a/sqlmock_test.go b/sqlmock_test.go index 97775df..ee6b516 100644 --- a/sqlmock_test.go +++ b/sqlmock_test.go @@ -1260,3 +1260,83 @@ func Test_sqlmock_Prepare_and_Exec(t *testing.T) { } defer rows.Close() } + +type failArgument struct{} + +func (f failArgument) Match(_ driver.Value) bool { + return false +} + +func Test_sqlmock_Exec(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() + + mock.ExpectBegin() + _, err = mock.(*sqlmock).Exec("", []driver.Value{}) + if err == nil { + t.Errorf("error expected") + return + } + + expected := NewResult(1, 1) + mock.ExpectExec("SELECT (.+) FROM users WHERE (.+)"). + WillReturnResult(expected). + WithArgs("test") + + matchErr := errors.New("matcher sqlmock.failArgument could not match 0 argument driver.NamedValue - {Name: Ordinal:1 Value:{}}") + mock.ExpectExec("SELECT (.+) FROM animals WHERE (.+)"). + WillReturnError(matchErr). + WithArgs(failArgument{}) + + mock.ExpectExec("").WithArgs(failArgument{}) + + mock.(*sqlmock).expected = mock.(*sqlmock).expected[1:] + query := "SELECT name, email FROM users WHERE name = ?" + result, err := mock.(*sqlmock).Exec(query, []driver.Value{"test"}) + if err != nil { + t.Error(err) + return + } + if !reflect.DeepEqual(result, expected) { + t.Errorf("Results are not equal. Expected: %v, Actual: %v", expected, result) + return + } + + failQuery := "SELECT name, sex FROM animals WHERE sex = ?" + _, err = mock.(*sqlmock).Exec(failQuery, []driver.Value{failArgument{}}) + if err == nil { + t.Errorf("error expected") + return + } + mock.(*sqlmock).ordered = false + _, err = mock.(*sqlmock).Exec("", []driver.Value{failArgument{}}) + if err == nil { + t.Errorf("error expected") + return + } +} + +func Test_sqlmock_Query(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) + query := "SELECT name, email FROM users WHERE name = ?" + rows, err := mock.(*sqlmock).Query(query, []driver.Value{"test"}) + if err != nil { + t.Error(err) + return + } + defer rows.Close() + _, err = mock.(*sqlmock).Query(query, []driver.Value{failArgument{}}) + if err == nil { + t.Errorf("error expected") + return + } +}