1
0
mirror of https://github.com/DATA-DOG/go-sqlmock.git synced 2025-04-15 11:36:45 +02:00

do not expose sql driver methods for sqlmock, give interface

This commit is contained in:
gedi 2015-08-28 11:06:14 +03:00
parent 711064c51d
commit febff80c09
7 changed files with 95 additions and 72 deletions

View File

@ -11,7 +11,7 @@ var pool *mockDriver
func init() { func init() {
pool = &mockDriver{ pool = &mockDriver{
conns: make(map[string]*Sqlmock), conns: make(map[string]*sqlmock),
} }
sql.Register("sqlmock", pool) sql.Register("sqlmock", pool)
} }
@ -19,7 +19,7 @@ func init() {
type mockDriver struct { type mockDriver struct {
sync.Mutex sync.Mutex
counter int counter int
conns map[string]*Sqlmock conns map[string]*sqlmock
} }
func (d *mockDriver) Open(dsn string) (driver.Conn, error) { func (d *mockDriver) Open(dsn string) (driver.Conn, error) {
@ -39,18 +39,18 @@ func (d *mockDriver) Open(dsn string) (driver.Conn, error) {
// and a mock to manage expectations. // and a mock to manage expectations.
// Pings db so that all expectations could be // Pings db so that all expectations could be
// asserted. // asserted.
func New() (db *sql.DB, mock *Sqlmock, err error) { func New() (db *sql.DB, mock Sqlmock, err error) {
pool.Lock() pool.Lock()
dsn := fmt.Sprintf("sqlmock_db_%d", pool.counter) dsn := fmt.Sprintf("sqlmock_db_%d", pool.counter)
pool.counter++ pool.counter++
mock = &Sqlmock{dsn: dsn, drv: pool, MatchExpectationsInOrder: true} smock := &sqlmock{dsn: dsn, drv: pool, ordered: true}
pool.conns[dsn] = mock pool.conns[dsn] = smock
pool.Unlock() pool.Unlock()
db, err = sql.Open("sqlmock", dsn) db, err = sql.Open("sqlmock", dsn)
if err != nil { if err != nil {
return return
} }
return db, mock, db.Ping() return db, smock, db.Ping()
} }

View File

@ -25,14 +25,15 @@ func TestShouldOpenConnectionIssue15(t *testing.T) {
t.Errorf("expected 1 connection in pool, but there is: %d", len(pool.conns)) t.Errorf("expected 1 connection in pool, but there is: %d", len(pool.conns))
} }
if mock.opened != 1 { smock, _ := mock.(*sqlmock)
t.Errorf("expected 1 connection on mock to be opened, but there is: %d", mock.opened) if smock.opened != 1 {
t.Errorf("expected 1 connection on mock to be opened, but there is: %d", smock.opened)
} }
// defer so the rows gets closed first // defer so the rows gets closed first
defer func() { defer func() {
if mock.opened != 0 { if smock.opened != 0 {
t.Errorf("expected no connections on mock to be opened, but there is: %d", mock.opened) t.Errorf("expected no connections on mock to be opened, but there is: %d", smock.opened)
} }
}() }()
@ -49,8 +50,8 @@ func TestShouldOpenConnectionIssue15(t *testing.T) {
} }
// now there should be two connections open // now there should be two connections open
if mock.opened != 2 { if smock.opened != 2 {
t.Errorf("expected 2 connection on mock to be opened, but there is: %d", mock.opened) t.Errorf("expected 2 connection on mock to be opened, but there is: %d", smock.opened)
} }
mock.ExpectClose() mock.ExpectClose()
@ -59,8 +60,8 @@ func TestShouldOpenConnectionIssue15(t *testing.T) {
} }
// one is still reserved for rows // one is still reserved for rows
if mock.opened != 1 { if smock.opened != 1 {
t.Errorf("expected 1 connection on mock to be still reserved for rows, but there is: %d", mock.opened) t.Errorf("expected 1 connection on mock to be still reserved for rows, but there is: %d", smock.opened)
} }
} }

View File

@ -247,7 +247,7 @@ func (e *ExpectedExec) WillReturnResult(result driver.Result) *ExpectedExec {
// Returned by *Sqlmock.ExpectPrepare. // Returned by *Sqlmock.ExpectPrepare.
type ExpectedPrepare struct { type ExpectedPrepare struct {
commonExpectation commonExpectation
mock *Sqlmock mock *sqlmock
sqlRegex *regexp.Regexp sqlRegex *regexp.Regexp
statement driver.Stmt statement driver.Stmt
closeErr error closeErr error

View File

@ -6,7 +6,7 @@ import (
) )
// used for examples // used for examples
var mock = &Sqlmock{} var mock = &sqlmock{}
func ExampleNewErrorResult() { func ExampleNewErrorResult() {
db, mock, _ := New() db, mock, _ := New()

View File

@ -18,11 +18,48 @@ import (
"regexp" "regexp"
) )
// Sqlmock type satisfies required sql.driver interfaces // Sqlmock interface serves to create expectations
// to simulate actual database and also serves to // for any kind of database action in order to mock
// create expectations for any kind of database action // and test real database behavior.
// in order to mock and test real database behavior. type Sqlmock interface {
type Sqlmock struct {
// ExpectClose queues an expectation for this database
// action to be triggered. the *ExpectedClose allows
// to mock database response
ExpectClose() *ExpectedClose
// ExpectationsWereMet checks whether all queued expectations
// were met in order. If any of them was not met - an error is returned.
ExpectationsWereMet() error
// ExpectPrepare expects Prepare() to be called with sql query
// which match sqlRegexStr given regexp.
// the *ExpectedPrepare allows to mock database response.
// Note that you may expect Query() or Exec() on the *ExpectedPrepare
// statement to prevent repeating sqlRegexStr
ExpectPrepare(sqlRegexStr string) *ExpectedPrepare
// ExpectQuery expects Query() or QueryRow() to be called with sql query
// which match sqlRegexStr given regexp.
// the *ExpectedQuery allows to mock database response.
ExpectQuery(sqlRegexStr string) *ExpectedQuery
// ExpectExec expects Exec() to be called with sql query
// which match sqlRegexStr given regexp.
// the *ExpectedExec allows to mock database response
ExpectExec(sqlRegexStr string) *ExpectedExec
// ExpectBegin expects *sql.DB.Begin to be called.
// the *ExpectedBegin allows to mock database response
ExpectBegin() *ExpectedBegin
// ExpectCommit expects *sql.Tx.Commit to be called.
// the *ExpectedCommit allows to mock database response
ExpectCommit() *ExpectedCommit
// ExpectRollback expects *sql.Tx.Rollback to be called.
// the *ExpectedRollback allows to mock database response
ExpectRollback() *ExpectedRollback
// MatchExpectationsInOrder gives an option whether to match all // MatchExpectationsInOrder gives an option whether to match all
// expectations in the order they were set or not. // expectations in the order they were set or not.
@ -30,29 +67,33 @@ type Sqlmock struct {
// By default it is set to - true. But if you use goroutines // By default it is set to - true. But if you use goroutines
// to parallelize your query executation, that option may // to parallelize your query executation, that option may
// be handy. // be handy.
MatchExpectationsInOrder bool MatchExpectationsInOrder(bool)
}
dsn string type sqlmock struct {
opened int ordered bool
drv *mockDriver dsn string
opened int
drv *mockDriver
expected []expectation expected []expectation
} }
// ExpectClose queues an expectation for this database func (c *sqlmock) ExpectClose() *ExpectedClose {
// action to be triggered. the *ExpectedClose allows
// to mock database response
func (c *Sqlmock) ExpectClose() *ExpectedClose {
e := &ExpectedClose{} e := &ExpectedClose{}
c.expected = append(c.expected, e) c.expected = append(c.expected, e)
return e return e
} }
func (c *sqlmock) MatchExpectationsInOrder(b bool) {
c.ordered = b
}
// Close a mock database driver connection. It may or may not // Close a mock database driver connection. It may or may not
// be called depending on the sircumstances, but if it is called // be called depending on the sircumstances, but if it is called
// there must be an *ExpectedClose expectation satisfied. // there must be an *ExpectedClose expectation satisfied.
// meets http://golang.org/pkg/database/sql/driver/#Conn interface // meets http://golang.org/pkg/database/sql/driver/#Conn interface
func (c *Sqlmock) Close() error { func (c *sqlmock) Close() error {
c.drv.Lock() c.drv.Lock()
defer c.drv.Unlock() defer c.drv.Unlock()
@ -77,7 +118,7 @@ func (c *Sqlmock) Close() error {
} }
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.ordered {
return fmt.Errorf("call to database Close, was not expected, next expectation is: %s", next) return fmt.Errorf("call to database Close, was not expected, next expectation is: %s", next)
} }
} }
@ -95,9 +136,7 @@ func (c *Sqlmock) Close() error {
return expected.err return expected.err
} }
// ExpectationsWereMet checks whether all queued expectations func (c *sqlmock) ExpectationsWereMet() error {
// were met in order. If any of them was not met - an error is returned.
func (c *Sqlmock) ExpectationsWereMet() error {
for _, e := range c.expected { for _, e := range c.expected {
if !e.fulfilled() { if !e.fulfilled() {
return fmt.Errorf("there is a remaining expectation which was not matched: %s", e) return fmt.Errorf("there is a remaining expectation which was not matched: %s", e)
@ -107,7 +146,7 @@ func (c *Sqlmock) ExpectationsWereMet() error {
} }
// Begin meets http://golang.org/pkg/database/sql/driver/#Conn interface // Begin meets http://golang.org/pkg/database/sql/driver/#Conn interface
func (c *Sqlmock) Begin() (driver.Tx, error) { func (c *sqlmock) Begin() (driver.Tx, error) {
var expected *ExpectedBegin var expected *ExpectedBegin
var ok bool var ok bool
var fulfilled int var fulfilled int
@ -124,7 +163,7 @@ func (c *Sqlmock) Begin() (driver.Tx, error) {
} }
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.ordered {
return nil, fmt.Errorf("call to database transaction Begin, was not expected, next expectation is: %s", next) return nil, fmt.Errorf("call to database transaction Begin, was not expected, next expectation is: %s", next)
} }
} }
@ -141,16 +180,14 @@ func (c *Sqlmock) Begin() (driver.Tx, error) {
return c, expected.err return c, expected.err
} }
// ExpectBegin expects *sql.DB.Begin to be called. func (c *sqlmock) ExpectBegin() *ExpectedBegin {
// the *ExpectedBegin allows to mock database response
func (c *Sqlmock) ExpectBegin() *ExpectedBegin {
e := &ExpectedBegin{} e := &ExpectedBegin{}
c.expected = append(c.expected, e) c.expected = append(c.expected, e)
return e return e
} }
// Exec meets http://golang.org/pkg/database/sql/driver/#Execer // Exec meets http://golang.org/pkg/database/sql/driver/#Execer
func (c *Sqlmock) Exec(query string, args []driver.Value) (res driver.Result, err error) { func (c *sqlmock) Exec(query string, args []driver.Value) (res driver.Result, err error) {
query = stripQuery(query) query = stripQuery(query)
var expected *ExpectedExec var expected *ExpectedExec
var fulfilled int var fulfilled int
@ -163,7 +200,7 @@ func (c *Sqlmock) Exec(query string, args []driver.Value) (res driver.Result, er
continue continue
} }
if c.MatchExpectationsInOrder { if c.ordered {
if expected, ok = next.(*ExpectedExec); ok { if expected, ok = next.(*ExpectedExec); ok {
break break
} }
@ -218,10 +255,7 @@ func (c *Sqlmock) Exec(query string, args []driver.Value) (res driver.Result, er
return expected.result, err return expected.result, err
} }
// ExpectExec expects Exec() to be called with sql query func (c *sqlmock) ExpectExec(sqlRegexStr string) *ExpectedExec {
// which match sqlRegexStr given regexp.
// the *ExpectedExec allows to mock database response
func (c *Sqlmock) ExpectExec(sqlRegexStr string) *ExpectedExec {
e := &ExpectedExec{} e := &ExpectedExec{}
e.sqlRegex = regexp.MustCompile(sqlRegexStr) e.sqlRegex = regexp.MustCompile(sqlRegexStr)
c.expected = append(c.expected, e) c.expected = append(c.expected, e)
@ -229,7 +263,7 @@ func (c *Sqlmock) ExpectExec(sqlRegexStr string) *ExpectedExec {
} }
// Prepare meets http://golang.org/pkg/database/sql/driver/#Conn interface // Prepare meets http://golang.org/pkg/database/sql/driver/#Conn interface
func (c *Sqlmock) Prepare(query string) (driver.Stmt, error) { func (c *sqlmock) Prepare(query string) (driver.Stmt, error) {
var expected *ExpectedPrepare var expected *ExpectedPrepare
var fulfilled int var fulfilled int
var ok bool var ok bool
@ -246,7 +280,7 @@ func (c *Sqlmock) Prepare(query string) (driver.Stmt, error) {
} }
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.ordered {
return nil, fmt.Errorf("call to Prepare stetement with query '%s', was not expected, next expectation is: %s", query, next) return nil, fmt.Errorf("call to Prepare stetement with query '%s', was not expected, next expectation is: %s", query, next)
} }
} }
@ -265,19 +299,14 @@ func (c *Sqlmock) Prepare(query string) (driver.Stmt, error) {
return &statement{c, query, expected.closeErr}, expected.err return &statement{c, query, expected.closeErr}, expected.err
} }
// ExpectPrepare expects Prepare() to be called with sql query func (c *sqlmock) ExpectPrepare(sqlRegexStr string) *ExpectedPrepare {
// which match sqlRegexStr given regexp.
// the *ExpectedPrepare allows to mock database response.
// Note that you may expect Query() or Exec() on the *ExpectedPrepare
// statement to prevent repeating sqlRegexStr
func (c *Sqlmock) ExpectPrepare(sqlRegexStr string) *ExpectedPrepare {
e := &ExpectedPrepare{sqlRegex: regexp.MustCompile(sqlRegexStr), mock: c} e := &ExpectedPrepare{sqlRegex: regexp.MustCompile(sqlRegexStr), mock: c}
c.expected = append(c.expected, e) c.expected = append(c.expected, e)
return e return e
} }
// Query meets http://golang.org/pkg/database/sql/driver/#Queryer // Query meets http://golang.org/pkg/database/sql/driver/#Queryer
func (c *Sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err error) { func (c *sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err error) {
query = stripQuery(query) query = stripQuery(query)
var expected *ExpectedQuery var expected *ExpectedQuery
var fulfilled int var fulfilled int
@ -290,7 +319,7 @@ func (c *Sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err
continue continue
} }
if c.MatchExpectationsInOrder { if c.ordered {
if expected, ok = next.(*ExpectedQuery); ok { if expected, ok = next.(*ExpectedQuery); ok {
break break
} }
@ -347,34 +376,27 @@ func (c *Sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err
return expected.rows, err return expected.rows, err
} }
// ExpectQuery expects Query() or QueryRow() to be called with sql query func (c *sqlmock) ExpectQuery(sqlRegexStr string) *ExpectedQuery {
// which match sqlRegexStr given regexp.
// the *ExpectedQuery allows to mock database response.
func (c *Sqlmock) ExpectQuery(sqlRegexStr string) *ExpectedQuery {
e := &ExpectedQuery{} e := &ExpectedQuery{}
e.sqlRegex = regexp.MustCompile(sqlRegexStr) e.sqlRegex = regexp.MustCompile(sqlRegexStr)
c.expected = append(c.expected, e) c.expected = append(c.expected, e)
return e return e
} }
// ExpectCommit expects *sql.Tx.Commit to be called. func (c *sqlmock) ExpectCommit() *ExpectedCommit {
// the *ExpectedCommit allows to mock database response
func (c *Sqlmock) ExpectCommit() *ExpectedCommit {
e := &ExpectedCommit{} e := &ExpectedCommit{}
c.expected = append(c.expected, e) c.expected = append(c.expected, e)
return e return e
} }
// ExpectRollback expects *sql.Tx.Rollback to be called. func (c *sqlmock) ExpectRollback() *ExpectedRollback {
// the *ExpectedRollback allows to mock database response
func (c *Sqlmock) ExpectRollback() *ExpectedRollback {
e := &ExpectedRollback{} e := &ExpectedRollback{}
c.expected = append(c.expected, e) c.expected = append(c.expected, e)
return e return e
} }
// Commit meets http://golang.org/pkg/database/sql/driver/#Tx // Commit meets http://golang.org/pkg/database/sql/driver/#Tx
func (c *Sqlmock) Commit() error { func (c *sqlmock) Commit() error {
var expected *ExpectedCommit var expected *ExpectedCommit
var fulfilled int var fulfilled int
var ok bool var ok bool
@ -391,7 +413,7 @@ func (c *Sqlmock) Commit() error {
} }
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.ordered {
return fmt.Errorf("call to commit transaction, was not expected, next expectation is: %s", next) return fmt.Errorf("call to commit transaction, was not expected, next expectation is: %s", next)
} }
} }
@ -409,7 +431,7 @@ func (c *Sqlmock) Commit() error {
} }
// Rollback meets http://golang.org/pkg/database/sql/driver/#Tx // Rollback meets http://golang.org/pkg/database/sql/driver/#Tx
func (c *Sqlmock) Rollback() error { func (c *sqlmock) Rollback() error {
var expected *ExpectedRollback var expected *ExpectedRollback
var fulfilled int var fulfilled int
var ok bool var ok bool
@ -426,7 +448,7 @@ func (c *Sqlmock) Rollback() error {
} }
next.Unlock() next.Unlock()
if c.MatchExpectationsInOrder { if c.ordered {
return fmt.Errorf("call to rollback transaction, was not expected, next expectation is: %s", next) return fmt.Errorf("call to rollback transaction, was not expected, next expectation is: %s", next)
} }
} }

View File

@ -586,7 +586,7 @@ func TestGoroutineExecutionWithUnorderedExpectationMatching(t *testing.T) {
defer db.Close() defer db.Close()
// note this line is important for unordered expectation matching // note this line is important for unordered expectation matching
mock.MatchExpectationsInOrder = false mock.MatchExpectationsInOrder(false)
result := NewResult(1, 1) result := NewResult(1, 1)
@ -626,7 +626,7 @@ func ExampleSqlmock_goroutines() {
defer db.Close() defer db.Close()
// note this line is important for unordered expectation matching // note this line is important for unordered expectation matching
mock.MatchExpectationsInOrder = false mock.MatchExpectationsInOrder(false)
result := NewResult(1, 1) result := NewResult(1, 1)

View File

@ -5,7 +5,7 @@ import (
) )
type statement struct { type statement struct {
conn *Sqlmock conn *sqlmock
query string query string
err error err error
} }