From ee2ed8f2d522fc1724a2cbd262c7cfb0c5bba2ba Mon Sep 17 00:00:00 2001 From: gedi Date: Fri, 7 Feb 2014 08:58:27 +0200 Subject: [PATCH] add doc blocks, split files --- README.md | 10 ++-- connection.go | 119 +++++++++++++++++++++++++++++++++++++++ expectations.go | 7 ++- expectations_test.go | 5 -- result.go | 6 ++ rows.go | 4 ++ sqlmock.go | 129 +++---------------------------------------- util.go | 11 ++++ 8 files changed, 160 insertions(+), 131 deletions(-) create mode 100644 connection.go create mode 100644 util.go diff --git a/README.md b/README.md index 5457479..f5a6778 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ triggered should be mocked in order to pass a test. ## Install - go get github.com/l3pp4rd/go-sqlmock + go get github.com/DATA-DOG/go-sqlmock ## Use it with pleasure -An example of some database interaction which you want to test: +An example of some database interaction which you may want to test: ``` go package main @@ -142,7 +142,7 @@ package main import ( "database/sql" - "github.com/l3pp4rd/go-sqlmock" + "github.com/DATA-DOG/go-sqlmock" "testing" "fmt" ) @@ -292,7 +292,9 @@ to compare them correctly, this may be improved. ## Contributions -Feel free to open a pull request. +Feel free to open a pull request. Note, if you wish to contribute an extension to public (exported methods or types) - +please open an issue before, to discuss whether these changes can be accepted. All backward incompatible changes are +and will be treated cautiously ## License diff --git a/connection.go b/connection.go new file mode 100644 index 0000000..c4d51d7 --- /dev/null +++ b/connection.go @@ -0,0 +1,119 @@ +package sqlmock + +import ( + "database/sql/driver" + "fmt" +) + +type conn struct { + expectations []expectation + active expectation +} + +// closes a mock database driver connection. It should +// be always called to ensure that all expectations +// were met successfully +func (c *conn) Close() (err error) { + for _, e := range mock.conn.expectations { + if !e.fulfilled() { + err = fmt.Errorf("There is a remaining expectation %T which was not matched yet", e) + break + } + } + mock.conn.expectations = []expectation{} + mock.conn.active = nil + return err +} + +func (c *conn) Begin() (driver.Tx, error) { + e := c.next() + if e == nil { + return nil, fmt.Errorf("All expectations were already fulfilled, call to Begin transaction was not expected") + } + + etb, ok := e.(*expectedBegin) + if !ok { + return nil, fmt.Errorf("Call to Begin transaction, was not expected, next expectation is %T as %+v", e, e) + } + etb.triggered = true + return &transaction{c}, etb.err +} + +// get next unfulfilled expectation +func (c *conn) next() (e expectation) { + for _, e = range c.expectations { + if !e.fulfilled() { + return + } + } + return nil // all expectations were fulfilled +} + +func (c *conn) Exec(query string, args []driver.Value) (driver.Result, error) { + e := c.next() + query = stripQuery(query) + if e == nil { + return nil, fmt.Errorf("All expectations were already fulfilled, call to Exec '%s' query with args %+v was not expected", query, args) + } + + eq, ok := e.(*expectedExec) + if !ok { + return nil, fmt.Errorf("Call to Exec query '%s' with args %+v, was not expected, next expectation is %T as %+v", query, args, e, e) + } + + eq.triggered = true + if eq.err != nil { + return nil, eq.err // mocked to return error + } + + if eq.result == nil { + return nil, fmt.Errorf("Exec query '%s' with args %+v, must return a database/sql/driver.Result, but it was not set for expectation %T as %+v", query, args, eq, eq) + } + + if !eq.queryMatches(query) { + return nil, fmt.Errorf("Exec query '%s', does not match regex '%s'", query, eq.sqlRegex.String()) + } + + if !eq.argsMatches(args) { + return nil, fmt.Errorf("Exec query '%s', args %+v does not match expected %+v", query, args, eq.args) + } + + return eq.result, nil +} + +func (c *conn) Prepare(query string) (driver.Stmt, error) { + return &statement{mock.conn, stripQuery(query)}, nil +} + +func (c *conn) Query(query string, args []driver.Value) (driver.Rows, error) { + e := c.next() + query = stripQuery(query) + if e == nil { + return nil, fmt.Errorf("All expectations were already fulfilled, call to Query '%s' with args %+v was not expected", query, args) + } + + eq, ok := e.(*expectedQuery) + if !ok { + return nil, fmt.Errorf("Call to Query '%s' with args %+v, was not expected, next expectation is %T as %+v", query, args, e, e) + } + + eq.triggered = true + if eq.err != nil { + return nil, eq.err // mocked to return error + } + + if eq.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, eq, eq) + } + + if !eq.queryMatches(query) { + return nil, fmt.Errorf("Query '%s', does not match regex [%s]", query, eq.sqlRegex.String()) + } + + if !eq.argsMatches(args) { + return nil, fmt.Errorf("Query '%s', args %+v does not match expected %+v", query, args, eq.args) + } + + return eq.rows, nil +} + diff --git a/expectations.go b/expectations.go index 38567b4..7d5c078 100644 --- a/expectations.go +++ b/expectations.go @@ -6,13 +6,14 @@ import ( "regexp" ) +// an expectation interface type expectation interface { fulfilled() bool setError(err error) } -// common expectation - +// common expectation struct +// satisfies the expectation interface type commonExpectation struct { triggered bool err error @@ -27,6 +28,7 @@ func (e *commonExpectation) setError(err error) { } // query based expectation +// adds a query matching logic type queryBasedExpectation struct { commonExpectation sqlRegex *regexp.Regexp @@ -99,3 +101,4 @@ type expectedExec struct { result driver.Result } + diff --git a/expectations_test.go b/expectations_test.go index e32c8d1..279e5ec 100644 --- a/expectations_test.go +++ b/expectations_test.go @@ -40,9 +40,4 @@ func TestQueryExpectationArgComparison(t *testing.T) { if !e.argsMatches(against) { t.Error("Arguments should match (time will be compared only by type), but it did not") } - - against = []driver.Value{5, 7899000} - if e.argsMatches(against) { - t.Error("Arguments should not match, but it did") - } } diff --git a/result.go b/result.go index 7aed686..5f2c447 100644 --- a/result.go +++ b/result.go @@ -1,10 +1,14 @@ package sqlmock +// a structure which implements database/sql/driver.Result +// holds last insert id and rows affected +// should be returned by Exec queries type Result struct { lastInsertId int64 rowsAffected int64 } +// creates a new result for Exec based query mocks func NewResult(lastInsertId int64, rowsAffected int64) *Result { return &Result{ lastInsertId, @@ -12,10 +16,12 @@ func NewResult(lastInsertId int64, rowsAffected int64) *Result { } } +// get last insert id func (res *Result) LastInsertId() (int64, error) { return res.lastInsertId, nil } +// get rows affected func (res *Result) RowsAffected() (int64, error) { return res.rowsAffected, nil } diff --git a/rows.go b/rows.go index f6a626c..fd08c80 100644 --- a/rows.go +++ b/rows.go @@ -7,6 +7,7 @@ import ( "strings" ) +// a struct which implements database/sql/driver.Rows type rows struct { cols []string rows [][]driver.Value @@ -25,6 +26,7 @@ func (r *rows) Err() error { return nil } +// advances to next row func (r *rows) Next(dest []driver.Value) error { r.pos++ if r.pos > len(r.rows) { @@ -38,6 +40,8 @@ func (r *rows) Next(dest []driver.Value) error { return nil } +// create rows from a csv string +// to be used for mocked queries func RowsFromCSVString(columns []string, s string) driver.Rows { rs := &rows{} rs.cols = columns diff --git a/sqlmock.go b/sqlmock.go index 70364d3..9fde320 100644 --- a/sqlmock.go +++ b/sqlmock.go @@ -3,14 +3,15 @@ package sqlmock import ( "database/sql" "database/sql/driver" - "errors" "fmt" "regexp" - "strings" ) var mock *mockDriver +// Mock interface defines a mock which is returned +// by any expectation and can be detailed further +// with the following methods type Mock interface { WithArgs(...driver.Value) Mock WillReturnError(error) Mock @@ -22,6 +23,7 @@ type mockDriver struct { conn *conn } +// opens a mock driver database connection func (d *mockDriver) Open(dsn string) (driver.Conn, error) { return mock.conn, nil } @@ -31,30 +33,7 @@ func init() { sql.Register("mock", mock) } -type conn struct { - expectations []expectation - active expectation -} - -func stripQuery(q string) (s string) { - s = strings.Replace(q, "\n", " ", -1) - s = strings.Replace(s, "\r", "", -1) - s = strings.TrimSpace(s) - return -} - -func (c *conn) Close() (err error) { - for _, e := range mock.conn.expectations { - if !e.fulfilled() { - err = errors.New(fmt.Sprintf("There is a remaining expectation %T which was not matched yet", e)) - break - } - } - mock.conn.expectations = []expectation{} - mock.conn.active = nil - return err -} - +// expect transaction to be started func ExpectBegin() Mock { e := &expectedBegin{} mock.conn.expectations = append(mock.conn.expectations, e) @@ -62,6 +41,7 @@ func ExpectBegin() Mock { return mock.conn } +// expect transaction to be commited func ExpectCommit() Mock { e := &expectedCommit{} mock.conn.expectations = append(mock.conn.expectations, e) @@ -69,6 +49,7 @@ func ExpectCommit() Mock { return mock.conn } +// expect transaction to be rolled back func ExpectRollback() Mock { e := &expectedRollback{} mock.conn.expectations = append(mock.conn.expectations, e) @@ -81,62 +62,6 @@ func (c *conn) WillReturnError(err error) Mock { return c } -func (c *conn) Begin() (driver.Tx, error) { - e := c.next() - if e == nil { - return nil, errors.New("All expectations were already fulfilled, call to Begin transaction was not expected") - } - - etb, ok := e.(*expectedBegin) - if !ok { - return nil, errors.New(fmt.Sprintf("Call to Begin transaction, was not expected, next expectation is %+v", e)) - } - etb.triggered = true - return &transaction{c}, etb.err -} - -// get next unfulfilled expectation -func (c *conn) next() (e expectation) { - for _, e = range c.expectations { - if !e.fulfilled() { - return - } - } - return nil // all expectations were fulfilled -} - -func (c *conn) Exec(query string, args []driver.Value) (driver.Result, error) { - e := c.next() - query = stripQuery(query) - if e == nil { - return nil, errors.New(fmt.Sprintf("All expectations were already fulfilled, call to Exec '%s' query with args %+v was not expected", query, args)) - } - - eq, ok := e.(*expectedExec) - if !ok { - return nil, errors.New(fmt.Sprintf("Call to Exec query '%s' with args %+v, was not expected, next expectation is %+v", query, args, e)) - } - - eq.triggered = true - if eq.err != nil { - return nil, eq.err // mocked to return error - } - - if eq.result == nil { - return nil, errors.New(fmt.Sprintf("Exec query '%s' with args %+v, must return a database/sql/driver.Result, but it was not set for expectation %+v", query, args, eq)) - } - - if !eq.queryMatches(query) { - return nil, errors.New(fmt.Sprintf("Exec query '%s', does not match regex '%s'", query, eq.sqlRegex.String())) - } - - if !eq.argsMatches(args) { - return nil, errors.New(fmt.Sprintf("Exec query '%s', args %+v does not match expected %+v", query, args, eq.args)) - } - - return eq.result, nil -} - func ExpectExec(sqlRegexStr string) Mock { e := &expectedExec{} e.sqlRegex = regexp.MustCompile(sqlRegexStr) @@ -171,7 +96,7 @@ func (c *conn) WithArgs(args ...driver.Value) Mock { func (c *conn) WillReturnResult(result driver.Result) Mock { eq, ok := c.active.(*expectedExec) if !ok { - panic(fmt.Sprintf("driver.Result may be returned only by Exec expectations, current is %+v", c.active)) + panic(fmt.Sprintf("driver.Result may be returned only by Exec expectations, current is %T", c.active)) } eq.result = result return c @@ -180,44 +105,8 @@ func (c *conn) WillReturnResult(result driver.Result) Mock { func (c *conn) WillReturnRows(rows driver.Rows) Mock { eq, ok := c.active.(*expectedQuery) if !ok { - panic(fmt.Sprintf("driver.Rows may be returned only by Query expectations, current is %+v", c.active)) + panic(fmt.Sprintf("driver.Rows may be returned only by Query expectations, current is %T", c.active)) } eq.rows = rows return c } - -func (c *conn) Prepare(query string) (driver.Stmt, error) { - return &statement{mock.conn, stripQuery(query)}, nil -} - -func (c *conn) Query(query string, args []driver.Value) (driver.Rows, error) { - e := c.next() - query = stripQuery(query) - if e == nil { - return nil, errors.New(fmt.Sprintf("All expectations were already fulfilled, call to Query '%s' with args %+v was not expected", query, args)) - } - - eq, ok := e.(*expectedQuery) - if !ok { - return nil, errors.New(fmt.Sprintf("Call to Query '%s' with args %+v, was not expected, next expectation is %+v", query, args, e)) - } - - eq.triggered = true - if eq.err != nil { - return nil, eq.err // mocked to return error - } - - if eq.rows == nil { - return nil, errors.New(fmt.Sprintf("Query '%s' with args %+v, must return a database/sql/driver.Rows, but it was not set for expectation %+v", query, args, eq)) - } - - if !eq.queryMatches(query) { - return nil, errors.New(fmt.Sprintf("Query '%s', does not match regex [%s]", query, eq.sqlRegex.String())) - } - - if !eq.argsMatches(args) { - return nil, errors.New(fmt.Sprintf("Query '%s', args %+v does not match expected %+v", query, args, eq.args)) - } - - return eq.rows, nil -} diff --git a/util.go b/util.go new file mode 100644 index 0000000..3b43ecd --- /dev/null +++ b/util.go @@ -0,0 +1,11 @@ +package sqlmock + +import "strings" + +// strip out new lines and trim spaces +func stripQuery(q string) (s string) { + s = strings.Replace(q, "\n", " ", -1) + s = strings.Replace(s, "\r", "", -1) + s = strings.TrimSpace(s) + return +}