mirror of
https://github.com/zhashkevych/go-sqlxmock.git
synced 2024-11-16 17:41:57 +02:00
added jmoiron/sqlx support
This commit is contained in:
parent
f920cc853b
commit
01cac9ec0f
46
driver.go
46
driver.go
@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -52,6 +53,23 @@ func New(options ...func(*sqlmock) error) (*sql.DB, Sqlmock, error) {
|
||||
return smock.open(options)
|
||||
}
|
||||
|
||||
// Newx creates sqlmock database connection of *sqlx.DB type and a mock to manage expectations.
|
||||
// Accepts options, like ValueConverterOption, to use a ValueConverter from
|
||||
// a specific driver.
|
||||
// Pings db so that all expectations could be
|
||||
// asserted.
|
||||
func Newx(options ...func(*sqlmock) error) (*sqlx.DB, Sqlmock, error) {
|
||||
pool.Lock()
|
||||
dsn := fmt.Sprintf("sqlmock_db_%d", pool.counter)
|
||||
pool.counter++
|
||||
|
||||
smock := &sqlmock{dsn: dsn, drv: pool, ordered: true}
|
||||
pool.conns[dsn] = smock
|
||||
pool.Unlock()
|
||||
|
||||
return smock.openx(options)
|
||||
}
|
||||
|
||||
// NewWithDSN creates sqlmock database connection with a specific DSN
|
||||
// and a mock to manage expectations.
|
||||
// Accepts options, like ValueConverterOption, to use a ValueConverter from
|
||||
@ -79,3 +97,31 @@ func NewWithDSN(dsn string, options ...func(*sqlmock) error) (*sql.DB, Sqlmock,
|
||||
|
||||
return smock.open(options)
|
||||
}
|
||||
|
||||
// NewxWithDSN creates sqlmock database connection of *sqlx.DB type with a specific DSN
|
||||
// and a mock to manage expectations.
|
||||
// Accepts options, like ValueConverterOption, to use a ValueConverter from
|
||||
// a specific driver.
|
||||
// Pings db so that all expectations could be asserted.
|
||||
//
|
||||
// This method is introduced because of sql abstraction
|
||||
// libraries, which do not provide a way to initialize
|
||||
// with sql.DB instance. For example GORM library.
|
||||
//
|
||||
// Note, it will error if attempted to create with an
|
||||
// already used dsn
|
||||
//
|
||||
// It is not recommended to use this method, unless you
|
||||
// really need it and there is no other way around.
|
||||
func NewxWithDSN(dsn string, options ...func(*sqlmock) error) (*sqlx.DB, Sqlmock, error) {
|
||||
pool.Lock()
|
||||
if _, ok := pool.conns[dsn]; ok {
|
||||
pool.Unlock()
|
||||
return nil, nil, fmt.Errorf("cannot create a new mock database with the same dsn: %s", dsn)
|
||||
}
|
||||
smock := &sqlmock{dsn: dsn, drv: pool, ordered: true}
|
||||
pool.conns[dsn] = smock
|
||||
pool.Unlock()
|
||||
|
||||
return smock.openx(options)
|
||||
}
|
||||
|
7
go.mod
7
go.mod
@ -1 +1,8 @@
|
||||
module github.com/DATA-DOG/go-sqlmock
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49
|
||||
)
|
||||
|
9
go.sum
Normal file
9
go.sum
Normal file
@ -0,0 +1,9 @@
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49 h1:o/c0aWEP/m6n61xlYW2QP4t9424qlJOsxugn5Zds2Rg=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
30
sqlmock.go
30
sqlmock.go
@ -14,6 +14,7 @@ import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -127,6 +128,35 @@ func (c *sqlmock) open(options []func(*sqlmock) error) (*sql.DB, Sqlmock, error)
|
||||
return db, c, db.Ping()
|
||||
}
|
||||
|
||||
func (c *sqlmock) openx(options []func(*sqlmock) error) (*sqlx.DB, Sqlmock, error) {
|
||||
db, err := sqlx.Open("sqlmock", c.dsn)
|
||||
if err != nil {
|
||||
return db, c, err
|
||||
}
|
||||
for _, option := range options {
|
||||
err := option(c)
|
||||
if err != nil {
|
||||
return db, c, err
|
||||
}
|
||||
}
|
||||
if c.converter == nil {
|
||||
c.converter = driver.DefaultParameterConverter
|
||||
}
|
||||
if c.queryMatcher == nil {
|
||||
c.queryMatcher = QueryMatcherRegexp
|
||||
}
|
||||
|
||||
if c.monitorPings {
|
||||
// We call Ping on the driver shortly to verify startup assertions by
|
||||
// driving internal behaviour of the sql standard library. We don't
|
||||
// want this call to ping to be monitored for expectation purposes so
|
||||
// temporarily disable.
|
||||
c.monitorPings = false
|
||||
defer func() { c.monitorPings = true }()
|
||||
}
|
||||
return db, c, db.Ping()
|
||||
}
|
||||
|
||||
func (c *sqlmock) ExpectClose() *ExpectedClose {
|
||||
e := &ExpectedClose{}
|
||||
c.expected = append(c.expected, e)
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
@ -22,6 +23,16 @@ func cancelOrder(db *sql.DB, orderID int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cancelOrderSQLX(db *sqlx.DB, orderID int) error {
|
||||
tx, _ := db.Begin()
|
||||
_, _ = tx.Query("SELECT * FROM orders {0} FOR UPDATE", orderID)
|
||||
err := tx.Rollback()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Example() {
|
||||
// Open new mock database
|
||||
db, mock, err := New()
|
||||
@ -56,6 +67,40 @@ func Example() {
|
||||
// Output:
|
||||
}
|
||||
|
||||
func ExampleSQLX() {
|
||||
// Open new mock database
|
||||
db, mock, err := Newx()
|
||||
if err != nil {
|
||||
fmt.Println("error creating mock database")
|
||||
return
|
||||
}
|
||||
// columns to be used for result
|
||||
columns := []string{"id", "status"}
|
||||
// expect transaction begin
|
||||
mock.ExpectBegin()
|
||||
// expect query to fetch order, match it with regexp
|
||||
mock.ExpectQuery("SELECT (.+) FROM orders (.+) FOR UPDATE").
|
||||
WithArgs(1).
|
||||
WillReturnRows(NewRows(columns).AddRow(1, 1))
|
||||
// expect transaction rollback, since order status is "cancelled"
|
||||
mock.ExpectRollback()
|
||||
|
||||
// run the cancel order function
|
||||
someOrderID := 1
|
||||
// call a function which executes expected database operations
|
||||
err = cancelOrderSQLX(db, someOrderID)
|
||||
if err != nil {
|
||||
fmt.Printf("unexpected error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// ensure all expectations have been met
|
||||
if err = mock.ExpectationsWereMet(); err != nil {
|
||||
fmt.Printf("unmet expectation error: %s", err)
|
||||
}
|
||||
// Output:
|
||||
}
|
||||
|
||||
func TestIssue14EscapeSQL(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, mock, err := New()
|
||||
@ -1220,6 +1265,53 @@ func queryWithTimeout(t time.Duration, db *sql.DB, query string, args ...interfa
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryWithTimeoutSQLX(t *testing.T) {
|
||||
db, mock, err := Newx()
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rs := NewRows([]string{"id", "title"}).FromCSVString("5,hello world")
|
||||
|
||||
mock.ExpectQuery("SELECT (.+) FROM articles WHERE id = ?").
|
||||
WillDelayFor(15 * time.Millisecond). // Query will take longer than timeout
|
||||
WithArgs(5).
|
||||
WillReturnRows(rs)
|
||||
|
||||
_, err = queryWithTimeoutSQLX(10*time.Millisecond, db, "SELECT (.+) FROM articles WHERE id = ?", 5)
|
||||
if err == nil {
|
||||
t.Errorf("expecting query to time out")
|
||||
}
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("there were unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func queryWithTimeoutSQLX(t time.Duration, db *sqlx.DB, query string, args ...interface{}) (*sql.Rows, error) {
|
||||
rowsChan := make(chan *sql.Rows, 1)
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
rows, err := db.Query(query, args...)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
rowsChan <- rows
|
||||
}()
|
||||
|
||||
select {
|
||||
case rows := <-rowsChan:
|
||||
return rows, nil
|
||||
case err := <-errChan:
|
||||
return nil, err
|
||||
case <-time.After(t):
|
||||
return nil, fmt.Errorf("query timed out after %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sqlmock_Prepare_and_Exec(t *testing.T) {
|
||||
db, mock, err := New()
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user