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:
parent
2fc5a0dd15
commit
ee2ed8f2d5
10
README.md
10
README.md
@ -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
119
connection.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
4
rows.go
4
rows.go
@ -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
|
||||||
|
129
sqlmock.go
129
sqlmock.go
@ -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
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user