1
0
mirror of https://github.com/DATA-DOG/go-sqlmock.git synced 2025-06-14 23:55:00 +02:00

concurrency support, closes #20 and closes #9 and closes #15

* c600769 do not require a connection name, unique dsn is generated
* 1b20b9c update travis
* 1097b6a add comments for godoc documentation
* c142a95 fix golint reported issues
This commit is contained in:
gedi
2015-07-17 13:14:30 +03:00
parent ed4836e31d
commit a071483cba
16 changed files with 725 additions and 911 deletions

View File

@ -6,190 +6,280 @@ It hooks into Go standard library's database/sql package.
The package provides convenient methods to mock database queries, transactions and
expect the right execution flow, compare query arguments or even return error instead
to simulate failures. See the example bellow, which illustrates how convenient it is
to work with:
package main
import (
"database/sql"
"github.com/DATA-DOG/go-sqlmock"
"testing"
"fmt"
)
// will test that order with a different status, cannot be cancelled
func TestShouldNotCancelOrderWithNonPendingStatus(t *testing.T) {
// open database stub
db, err := sql.Open("mock", "")
if err != nil {
t.Errorf("An error '%s' was not expected when opening a stub database connection", err)
}
// columns to be used for result
columns := []string{"id", "status"}
// expect transaction begin
sqlmock.ExpectBegin()
// expect query to fetch order, match it with regexp
sqlmock.ExpectQuery("SELECT (.+) FROM orders (.+) FOR UPDATE").
WithArgs(1).
WillReturnRows(sqlmock.NewRows(columns).FromCSVString("1,1"))
// expect transaction rollback, since order status is "cancelled"
sqlmock.ExpectRollback()
// run the cancel order function
someOrderId := 1
// call a function which executes expected database operations
err = cancelOrder(someOrderId, db)
if err != nil {
t.Errorf("Expected no error, but got %s instead", err)
}
// db.Close() ensures that all expectations have been met
if err = db.Close(); err != nil {
t.Errorf("Error '%s' was not expected while closing the database", err)
}
}
to work with.
*/
package sqlmock
import (
"database/sql"
"database/sql/driver"
"fmt"
"reflect"
"regexp"
)
var mock *mockDriver
// Sqlmock type satisfies required sql.driver interfaces
// to simulate actual database and also serves to
// create expectations for any kind of database action
// in order to mock and test real database behavior.
type Sqlmock struct {
dsn string
opened int
drv *mockDriver
// Mock interface defines a mock which is returned
// by any expectation and can be detailed further
// with the methods this interface provides
type Mock interface {
WithArgs(...driver.Value) Mock
WillReturnError(error) Mock
WillReturnRows(driver.Rows) Mock
WillReturnResult(driver.Result) Mock
expected []expectation
}
type mockDriver struct {
conn *conn
}
func (d *mockDriver) Open(dsn string) (driver.Conn, error) {
return mock.conn, nil
}
func init() {
mock = &mockDriver{&conn{}}
sql.Register("mock", mock)
}
// New creates sqlmock database connection
// and pings it so that all expectations could be
// asserted on Close.
func New() (db *sql.DB, err error) {
db, err = sql.Open("mock", "")
if err != nil {
return
}
// ensure open connection, otherwise Close does not assert expectations
return db, db.Ping()
}
// ExpectBegin expects transaction to be started
func ExpectBegin() Mock {
e := &expectedBegin{}
mock.conn.expectations = append(mock.conn.expectations, e)
mock.conn.active = e
return mock.conn
}
// ExpectCommit expects transaction to be commited
func ExpectCommit() Mock {
e := &expectedCommit{}
mock.conn.expectations = append(mock.conn.expectations, e)
mock.conn.active = e
return mock.conn
}
// ExpectRollback expects transaction to be rolled back
func ExpectRollback() Mock {
e := &expectedRollback{}
mock.conn.expectations = append(mock.conn.expectations, e)
mock.conn.active = e
return mock.conn
}
// ExpectPrepare expects Query to be prepared
func ExpectPrepare() Mock {
e := &expectedPrepare{}
mock.conn.expectations = append(mock.conn.expectations, e)
mock.conn.active = e
return mock.conn
}
// WillReturnError the expectation will return an error
func (c *conn) WillReturnError(err error) Mock {
c.active.setError(err)
return c
}
// ExpectExec expects database Exec to be triggered, which will match
// the given query string as a regular expression
func ExpectExec(sqlRegexStr string) Mock {
e := &expectedExec{}
e.sqlRegex = regexp.MustCompile(sqlRegexStr)
mock.conn.expectations = append(mock.conn.expectations, e)
mock.conn.active = e
return mock.conn
}
// ExpectQuery database Query to be triggered, which will match
// the given query string as a regular expression
func ExpectQuery(sqlRegexStr string) Mock {
e := &expectedQuery{}
e.sqlRegex = regexp.MustCompile(sqlRegexStr)
mock.conn.expectations = append(mock.conn.expectations, e)
mock.conn.active = e
return mock.conn
}
// WithArgs expectation should be called with given arguments.
// Works with Exec and Query expectations
func (c *conn) WithArgs(args ...driver.Value) Mock {
eq, ok := c.active.(*expectedQuery)
if !ok {
ee, ok := c.active.(*expectedExec)
if !ok {
panic(fmt.Sprintf("arguments may be expected only with query based expectations, current is %T", c.active))
func (c *Sqlmock) next() (e expectation) {
for _, e = range c.expected {
if !e.fulfilled() {
return
}
ee.args = args
} else {
eq.args = args
}
return c
return nil // all expectations were fulfilled
}
// WillReturnResult expectation will return a Result.
// Works only with Exec expectations
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 %T", c.active))
}
eq.result = result
return c
// ExpectClose queues an expectation for this database
// action to be triggered. the *ExpectedClose allows
// to mock database response
func (c *Sqlmock) ExpectClose() *ExpectedClose {
e := &ExpectedClose{}
c.expected = append(c.expected, e)
return e
}
// WillReturnRows expectation will return Rows.
// Works only with Query expectations
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 %T", c.active))
// Close a mock database driver connection. It may or may not
// be called depending on the sircumstances, but if it is called
// there must be an *ExpectedClose expectation satisfied.
// meets http://golang.org/pkg/database/sql/driver/#Conn interface
func (c *Sqlmock) Close() error {
c.drv.Lock()
defer c.drv.Unlock()
c.opened--
if c.opened == 0 {
delete(c.drv.conns, c.dsn)
}
e := c.next()
if e == nil {
return fmt.Errorf("all expectations were already fulfilled, call to database Close was not expected")
}
t, ok := e.(*ExpectedClose)
if !ok {
return fmt.Errorf("call to database Close, was not expected, next expectation is %T as %+v", e, e)
}
t.triggered = true
return t.err
}
// ExpectationsWereMet checks whether all queued expectations
// 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 {
if !e.fulfilled() {
return fmt.Errorf("there is a remaining expectation %T which was not matched yet", e)
}
}
return nil
}
// Begin meets http://golang.org/pkg/database/sql/driver/#Conn interface
func (c *Sqlmock) 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")
}
t, ok := e.(*ExpectedBegin)
if !ok {
return nil, fmt.Errorf("call to begin transaction, was not expected, next expectation is %T as %+v", e, e)
}
t.triggered = true
return c, t.err
}
// ExpectBegin expects *sql.DB.Begin to be called.
// the *ExpectedBegin allows to mock database response
func (c *Sqlmock) ExpectBegin() *ExpectedBegin {
e := &ExpectedBegin{}
c.expected = append(c.expected, e)
return e
}
// Exec meets http://golang.org/pkg/database/sql/driver/#Execer
func (c *Sqlmock) Exec(query string, args []driver.Value) (res driver.Result, err 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)
}
t, 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)
}
t.triggered = true
if t.err != nil {
return nil, t.err // mocked to return error
}
if t.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, t, t)
}
defer argMatcherErrorHandler(&err) // converts panic to error in case of reflect value type mismatch
if !t.queryMatches(query) {
return nil, fmt.Errorf("exec query '%s', does not match regex '%s'", query, t.sqlRegex.String())
}
if !t.argsMatches(args) {
return nil, fmt.Errorf("exec query '%s', args %+v does not match expected %+v", query, args, t.args)
}
return t.result, err
}
// ExpectExec expects Exec() to be called with sql query
// which match sqlRegexStr given regexp.
// the *ExpectedExec allows to mock database response
func (c *Sqlmock) ExpectExec(sqlRegexStr string) *ExpectedExec {
e := &ExpectedExec{}
e.sqlRegex = regexp.MustCompile(sqlRegexStr)
c.expected = append(c.expected, e)
return e
}
// Prepare meets http://golang.org/pkg/database/sql/driver/#Conn interface
func (c *Sqlmock) Prepare(query string) (driver.Stmt, error) {
e := c.next()
query = stripQuery(query)
if e == nil {
return nil, fmt.Errorf("all expectations were already fulfilled, call to Prepare '%s' query was not expected", query)
}
t, ok := e.(*ExpectedPrepare)
if !ok {
return nil, fmt.Errorf("call to Prepare stetement with query '%s', was not expected, next expectation is %T as %+v", query, e, e)
}
t.triggered = true
if t.err != nil {
return nil, t.err // mocked to return error
}
return &statement{c, query, t.closeErr}, nil
}
// 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
func (c *Sqlmock) ExpectPrepare(sqlRegexStr string) *ExpectedPrepare {
e := &ExpectedPrepare{sqlRegex: regexp.MustCompile(sqlRegexStr), mock: c}
c.expected = append(c.expected, e)
return e
}
// Query meets http://golang.org/pkg/database/sql/driver/#Queryer
func (c *Sqlmock) Query(query string, args []driver.Value) (rw driver.Rows, err 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)
}
t, 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)
}
t.triggered = true
if t.err != nil {
return nil, t.err // mocked to return error
}
if t.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, t, t)
}
defer argMatcherErrorHandler(&err) // converts panic to error in case of reflect value type mismatch
if !t.queryMatches(query) {
return nil, fmt.Errorf("query '%s', does not match regex [%s]", query, t.sqlRegex.String())
}
if !t.argsMatches(args) {
return nil, fmt.Errorf("query '%s', args %+v does not match expected %+v", query, args, t.args)
}
return t.rows, err
}
// ExpectQuery expects Query() or QueryRow() to be called with sql query
// which match sqlRegexStr given regexp.
// the *ExpectedQuery allows to mock database response.
func (c *Sqlmock) ExpectQuery(sqlRegexStr string) *ExpectedQuery {
e := &ExpectedQuery{}
e.sqlRegex = regexp.MustCompile(sqlRegexStr)
c.expected = append(c.expected, e)
return e
}
// ExpectCommit expects *sql.Tx.Commit to be called.
// the *ExpectedCommit allows to mock database response
func (c *Sqlmock) ExpectCommit() *ExpectedCommit {
e := &ExpectedCommit{}
c.expected = append(c.expected, e)
return e
}
// ExpectRollback expects *sql.Tx.Rollback to be called.
// the *ExpectedRollback allows to mock database response
func (c *Sqlmock) ExpectRollback() *ExpectedRollback {
e := &ExpectedRollback{}
c.expected = append(c.expected, e)
return e
}
// Commit meets http://golang.org/pkg/database/sql/driver/#Tx
func (c *Sqlmock) Commit() error {
e := c.next()
if e == nil {
return fmt.Errorf("all expectations were already fulfilled, call to commit transaction was not expected")
}
t, ok := e.(*ExpectedCommit)
if !ok {
return fmt.Errorf("call to commit transaction, was not expected, next expectation was %v", e)
}
t.triggered = true
return t.err
}
// Rollback meets http://golang.org/pkg/database/sql/driver/#Tx
func (c *Sqlmock) Rollback() error {
e := c.next()
if e == nil {
return fmt.Errorf("all expectations were already fulfilled, call to rollback transaction was not expected")
}
t, ok := e.(*ExpectedRollback)
if !ok {
return fmt.Errorf("call to rollback transaction, was not expected, next expectation was %v", e)
}
t.triggered = true
return t.err
}
func argMatcherErrorHandler(errp *error) {
if e := recover(); e != nil {
if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion
*errp = fmt.Errorf("Failed to compare query arguments: %s", se)
} else {
panic(e) // overwise panic
}
}
eq.rows = rows
return c
}