1
0
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:
Maksim Zhashkevych 2020-10-22 11:48:58 +03:00
parent f920cc853b
commit 01cac9ec0f
5 changed files with 184 additions and 0 deletions

View File

@ -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
View File

@ -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
View 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=

View File

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

View File

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