1
0
mirror of https://github.com/DATA-DOG/go-sqlmock.git synced 2025-04-25 12:04:40 +02:00

add doc blocks, split files

This commit is contained in:
gedi 2014-02-07 08:58:27 +02:00
parent 2fc5a0dd15
commit ee2ed8f2d5
8 changed files with 160 additions and 131 deletions

View File

@ -6,11 +6,11 @@ triggered should be mocked in order to pass a test.
## Install ## Install
go get github.com/l3pp4rd/go-sqlmock go get github.com/DATA-DOG/go-sqlmock
## Use it with pleasure ## 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 ``` go
package main package main
@ -142,7 +142,7 @@ package main
import ( import (
"database/sql" "database/sql"
"github.com/l3pp4rd/go-sqlmock" "github.com/DATA-DOG/go-sqlmock"
"testing" "testing"
"fmt" "fmt"
) )
@ -292,7 +292,9 @@ to compare them correctly, this may be improved.
## Contributions ## 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 ## License

119
connection.go Normal file
View File

@ -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
}

View File

@ -6,13 +6,14 @@ import (
"regexp" "regexp"
) )
// an expectation interface
type expectation interface { type expectation interface {
fulfilled() bool fulfilled() bool
setError(err error) setError(err error)
} }
// common expectation // common expectation struct
// satisfies the expectation interface
type commonExpectation struct { type commonExpectation struct {
triggered bool triggered bool
err error err error
@ -27,6 +28,7 @@ func (e *commonExpectation) setError(err error) {
} }
// query based expectation // query based expectation
// adds a query matching logic
type queryBasedExpectation struct { type queryBasedExpectation struct {
commonExpectation commonExpectation
sqlRegex *regexp.Regexp sqlRegex *regexp.Regexp
@ -99,3 +101,4 @@ type expectedExec struct {
result driver.Result result driver.Result
} }

View File

@ -40,9 +40,4 @@ func TestQueryExpectationArgComparison(t *testing.T) {
if !e.argsMatches(against) { if !e.argsMatches(against) {
t.Error("Arguments should match (time will be compared only by type), but it did not") 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")
}
} }

View File

@ -1,10 +1,14 @@
package sqlmock 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 { type Result struct {
lastInsertId int64 lastInsertId int64
rowsAffected int64 rowsAffected int64
} }
// creates a new result for Exec based query mocks
func NewResult(lastInsertId int64, rowsAffected int64) *Result { func NewResult(lastInsertId int64, rowsAffected int64) *Result {
return &Result{ return &Result{
lastInsertId, lastInsertId,
@ -12,10 +16,12 @@ func NewResult(lastInsertId int64, rowsAffected int64) *Result {
} }
} }
// get last insert id
func (res *Result) LastInsertId() (int64, error) { func (res *Result) LastInsertId() (int64, error) {
return res.lastInsertId, nil return res.lastInsertId, nil
} }
// get rows affected
func (res *Result) RowsAffected() (int64, error) { func (res *Result) RowsAffected() (int64, error) {
return res.rowsAffected, nil return res.rowsAffected, nil
} }

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
) )
// a struct which implements database/sql/driver.Rows
type rows struct { type rows struct {
cols []string cols []string
rows [][]driver.Value rows [][]driver.Value
@ -25,6 +26,7 @@ func (r *rows) Err() error {
return nil return nil
} }
// advances to next row
func (r *rows) Next(dest []driver.Value) error { func (r *rows) Next(dest []driver.Value) error {
r.pos++ r.pos++
if r.pos > len(r.rows) { if r.pos > len(r.rows) {
@ -38,6 +40,8 @@ func (r *rows) Next(dest []driver.Value) error {
return nil return nil
} }
// create rows from a csv string
// to be used for mocked queries
func RowsFromCSVString(columns []string, s string) driver.Rows { func RowsFromCSVString(columns []string, s string) driver.Rows {
rs := &rows{} rs := &rows{}
rs.cols = columns rs.cols = columns

View File

@ -3,14 +3,15 @@ package sqlmock
import ( import (
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"errors"
"fmt" "fmt"
"regexp" "regexp"
"strings"
) )
var mock *mockDriver 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 { type Mock interface {
WithArgs(...driver.Value) Mock WithArgs(...driver.Value) Mock
WillReturnError(error) Mock WillReturnError(error) Mock
@ -22,6 +23,7 @@ type mockDriver struct {
conn *conn conn *conn
} }
// opens a mock driver database connection
func (d *mockDriver) Open(dsn string) (driver.Conn, error) { func (d *mockDriver) Open(dsn string) (driver.Conn, error) {
return mock.conn, nil return mock.conn, nil
} }
@ -31,30 +33,7 @@ func init() {
sql.Register("mock", mock) sql.Register("mock", mock)
} }
type conn struct { // expect transaction to be started
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
}
func ExpectBegin() Mock { func ExpectBegin() Mock {
e := &expectedBegin{} e := &expectedBegin{}
mock.conn.expectations = append(mock.conn.expectations, e) mock.conn.expectations = append(mock.conn.expectations, e)
@ -62,6 +41,7 @@ func ExpectBegin() Mock {
return mock.conn return mock.conn
} }
// expect transaction to be commited
func ExpectCommit() Mock { func ExpectCommit() Mock {
e := &expectedCommit{} e := &expectedCommit{}
mock.conn.expectations = append(mock.conn.expectations, e) mock.conn.expectations = append(mock.conn.expectations, e)
@ -69,6 +49,7 @@ func ExpectCommit() Mock {
return mock.conn return mock.conn
} }
// expect transaction to be rolled back
func ExpectRollback() Mock { func ExpectRollback() Mock {
e := &expectedRollback{} e := &expectedRollback{}
mock.conn.expectations = append(mock.conn.expectations, e) mock.conn.expectations = append(mock.conn.expectations, e)
@ -81,62 +62,6 @@ func (c *conn) WillReturnError(err error) Mock {
return c 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 { func ExpectExec(sqlRegexStr string) Mock {
e := &expectedExec{} e := &expectedExec{}
e.sqlRegex = regexp.MustCompile(sqlRegexStr) 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 { func (c *conn) WillReturnResult(result driver.Result) Mock {
eq, ok := c.active.(*expectedExec) eq, ok := c.active.(*expectedExec)
if !ok { 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 eq.result = result
return c return c
@ -180,44 +105,8 @@ func (c *conn) WillReturnResult(result driver.Result) Mock {
func (c *conn) WillReturnRows(rows driver.Rows) Mock { func (c *conn) WillReturnRows(rows driver.Rows) Mock {
eq, ok := c.active.(*expectedQuery) eq, ok := c.active.(*expectedQuery)
if !ok { 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 eq.rows = rows
return c 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
}

11
util.go Normal file
View File

@ -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
}