pax_global_header00006660000000000000000000000064125700131660014513gustar00rootroot0000000000000052 comment=febff80c09712044459f72847e56aaa69c9dad3b go-sqlmock-1.0.0/000077500000000000000000000000001257001316600135655ustar00rootroot00000000000000go-sqlmock-1.0.0/.gitignore000066400000000000000000000001021257001316600155460ustar00rootroot00000000000000/examples/blog/blog /examples/orders/orders /examples/basic/basic go-sqlmock-1.0.0/.travis.yml000066400000000000000000000001771257001316600157030ustar00rootroot00000000000000language: go sudo: false go: - 1.2 - 1.3 - 1.4 - release - tip script: - go test -v ./... - go test -race ./... go-sqlmock-1.0.0/LICENSE000066400000000000000000000027321257001316600145760ustar00rootroot00000000000000The three clause BSD license (http://en.wikipedia.org/wiki/BSD_licenses) Copyright (c) 2013-2015, DataDog.lt team All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name DataDog.lt may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. go-sqlmock-1.0.0/README.md000066400000000000000000000133211257001316600150440ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/DATA-DOG/go-sqlmock.png)](https://travis-ci.org/DATA-DOG/go-sqlmock) [![GoDoc](https://godoc.org/github.com/DATA-DOG/go-sqlmock?status.png)](https://godoc.org/github.com/DATA-DOG/go-sqlmock) # Sql driver mock for Golang This is a **mock** driver as **database/sql/driver** which is very flexible and pragmatic to manage and mock expected queries. All the expectations should be met and all queries and actions triggered should be mocked in order to pass a test. The package has no 3rd party dependencies. **NOTE:** regarding major issues #20 and #9 the **api** has changed to support concurrency and more than one database connection. If you need an old version, checkout **go-sqlmock** at gopkg.in: go get gopkg.in/DATA-DOG/go-sqlmock.v0 Otherwise use the **v1** branch from master which should be stable afterwards, because all the issues which were known will be fixed in this version. ## Install go get gopkg.in/DATA-DOG/go-sqlmock.v1 Or take an older version: go get gopkg.in/DATA-DOG/go-sqlmock.v0 ## Documentation and Examples Visit [godoc](http://godoc.org/github.com/DATA-DOG/go-sqlmock) for general examples and public api reference. See **.travis.yml** for supported **go** versions. Different use case, is to functionally test with a real database - [go-txdb](https://github.com/DATA-DOG/go-txdb) all database related actions are isolated within a single transaction so the database can remain in the same state. See implementation examples: - [blog API server](https://github.com/DATA-DOG/go-sqlmock/tree/master/examples/blog) - [the same orders example](https://github.com/DATA-DOG/go-sqlmock/tree/master/examples/orders) ### Something you may want to test ``` go package main import "database/sql" func recordStats(db *sql.DB, userID, productID int64) (err error) { tx, err := db.Begin() if err != nil { return } defer func() { switch err { case nil: err = tx.Commit() default: tx.Rollback() } }() if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil { return } if _, err = tx.Exec("INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)", userID, productID); err != nil { return } return } func main() { // @NOTE: the real connection is not required for tests db, err := sql.Open("mysql", "root@/blog") if err != nil { panic(err) } defer db.Close() if err = recordStats(db, 1 /*some user id*/, 5 /*some product id*/); err != nil { panic(err) } } ``` ### Tests with sqlmock ``` go package main import ( "fmt" "testing" "github.com/DATA-DOG/go-sqlmock" ) // a successful case func TestShouldUpdateStats(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() // now we execute our method if err = recordStats(db, 2, 3); err != nil { t.Errorf("error was not expected while updating stats: %s", err) } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } // a failing test case func TestShouldRollbackStatUpdatesOnFailure(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers"). WithArgs(2, 3). WillReturnError(fmt.Errorf("some error")) mock.ExpectRollback() // now we execute our method if err = recordStats(db, 2, 3); err == nil { t.Errorf("was expecting an error, but there was none") } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } ``` ## Run tests go test -race ## Changes - **2015-08-27** - **v1** api change, concurrency support, all known issues fixed. - **2014-08-16** instead of **panic** during reflect type mismatch when comparing query arguments - now return error - **2014-08-14** added **sqlmock.NewErrorResult** which gives an option to return driver.Result with errors for interface methods, see [issue](https://github.com/DATA-DOG/go-sqlmock/issues/5) - **2014-05-29** allow to match arguments in more sophisticated ways, by providing an **sqlmock.Argument** interface - **2014-04-21** introduce **sqlmock.New()** to open a mock database connection for tests. This method calls sql.DB.Ping to ensure that connection is open, see [issue](https://github.com/DATA-DOG/go-sqlmock/issues/4). This way on Close it will surely assert if all expectations are met, even if database was not triggered at all. The old way is still available, but it is advisable to call db.Ping manually before asserting with db.Close. - **2014-02-14** RowsFromCSVString is now a part of Rows interface named as FromCSVString. It has changed to allow more ways to construct rows and to easily extend this API in future. See [issue 1](https://github.com/DATA-DOG/go-sqlmock/issues/1) **RowsFromCSVString** is deprecated and will be removed in future ## Contributions 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 The [three clause BSD license](http://en.wikipedia.org/wiki/BSD_licenses) go-sqlmock-1.0.0/driver.go000066400000000000000000000017431257001316600154140ustar00rootroot00000000000000package sqlmock import ( "database/sql" "database/sql/driver" "fmt" "sync" ) var pool *mockDriver func init() { pool = &mockDriver{ conns: make(map[string]*sqlmock), } sql.Register("sqlmock", pool) } type mockDriver struct { sync.Mutex counter int conns map[string]*sqlmock } func (d *mockDriver) Open(dsn string) (driver.Conn, error) { d.Lock() defer d.Unlock() c, ok := d.conns[dsn] if !ok { return c, fmt.Errorf("expected a connection to be available, but it is not") } c.opened++ return c, nil } // New creates sqlmock database connection // and a mock to manage expectations. // Pings db so that all expectations could be // asserted. func New() (db *sql.DB, mock Sqlmock, err 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() db, err = sql.Open("sqlmock", dsn) if err != nil { return } return db, smock, db.Ping() } go-sqlmock-1.0.0/driver_test.go000066400000000000000000000042341257001316600164510ustar00rootroot00000000000000package sqlmock import ( "fmt" "testing" ) func ExampleNew() { db, mock, err := New() if err != nil { fmt.Println("expected no error, but got:", err) return } defer db.Close() // now we can expect operations performed on db mock.ExpectBegin().WillReturnError(fmt.Errorf("an error will occur on db.Begin() call")) } func TestShouldOpenConnectionIssue15(t *testing.T) { db, mock, err := New() if err != nil { t.Errorf("expected no error, but got: %s", err) } if len(pool.conns) != 1 { t.Errorf("expected 1 connection in pool, but there is: %d", len(pool.conns)) } smock, _ := mock.(*sqlmock) 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 func() { if smock.opened != 0 { t.Errorf("expected no connections on mock to be opened, but there is: %d", smock.opened) } }() mock.ExpectQuery("SELECT").WillReturnRows(NewRows([]string{"one", "two"}).AddRow("val1", "val2")) rows, err := db.Query("SELECT") if err != nil { t.Errorf("unexpected error: %s", err) } defer rows.Close() mock.ExpectExec("UPDATE").WillReturnResult(NewResult(1, 1)) if _, err = db.Exec("UPDATE"); err != nil { t.Errorf("unexpected error: %s", err) } // now there should be two connections open if smock.opened != 2 { t.Errorf("expected 2 connection on mock to be opened, but there is: %d", smock.opened) } mock.ExpectClose() if err = db.Close(); err != nil { t.Errorf("expected no error on close, but got: %s", err) } // one is still reserved for rows if smock.opened != 1 { t.Errorf("expected 1 connection on mock to be still reserved for rows, but there is: %d", smock.opened) } } func TestTwoOpenConnectionsOnTheSameDSN(t *testing.T) { db, mock, err := New() if err != nil { t.Errorf("expected no error, but got: %s", err) } db2, mock2, err := New() if len(pool.conns) != 2 { t.Errorf("expected 2 connection in pool, but there is: %d", len(pool.conns)) } if db == db2 { t.Errorf("expected not the same database instance, but it is the same") } if mock == mock2 { t.Errorf("expected not the same mock instance, but it is the same") } } go-sqlmock-1.0.0/examples/000077500000000000000000000000001257001316600154035ustar00rootroot00000000000000go-sqlmock-1.0.0/examples/basic/000077500000000000000000000000001257001316600164645ustar00rootroot00000000000000go-sqlmock-1.0.0/examples/basic/basic.go000066400000000000000000000013701257001316600200750ustar00rootroot00000000000000package main import "database/sql" func recordStats(db *sql.DB, userID, productID int64) (err error) { tx, err := db.Begin() if err != nil { return } defer func() { switch err { case nil: err = tx.Commit() default: tx.Rollback() } }() if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil { return } if _, err = tx.Exec("INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)", userID, productID); err != nil { return } return } func main() { // @NOTE: the real connection is not required for tests db, err := sql.Open("mysql", "root@/blog") if err != nil { panic(err) } defer db.Close() if err = recordStats(db, 1 /*some user id*/, 5 /*some product id*/); err != nil { panic(err) } } go-sqlmock-1.0.0/examples/basic/basic_test.go000066400000000000000000000031171257001316600211350ustar00rootroot00000000000000package main import ( "fmt" "testing" "github.com/DATA-DOG/go-sqlmock" ) // a successful case func TestShouldUpdateStats(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() // now we execute our method if err = recordStats(db, 2, 3); err != nil { t.Errorf("error was not expected while updating stats: %s", err) } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } // a failing test case func TestShouldRollbackStatUpdatesOnFailure(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers"). WithArgs(2, 3). WillReturnError(fmt.Errorf("some error")) mock.ExpectRollback() // now we execute our method if err = recordStats(db, 2, 3); err == nil { t.Errorf("was expecting an error, but there was none") } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } go-sqlmock-1.0.0/examples/blog/000077500000000000000000000000001257001316600163265ustar00rootroot00000000000000go-sqlmock-1.0.0/examples/blog/blog.go000066400000000000000000000030231257001316600175760ustar00rootroot00000000000000package main import ( "database/sql" "encoding/json" "net/http" ) type api struct { db *sql.DB } type post struct { ID int Title string Body string } func (a *api) posts(w http.ResponseWriter, r *http.Request) { rows, err := a.db.Query("SELECT id, title, body FROM posts") if err != nil { a.fail(w, "failed to fetch posts: "+err.Error(), 500) return } defer rows.Close() var posts []*post for rows.Next() { p := &post{} if err := rows.Scan(&p.ID, &p.Title, &p.Body); err != nil { a.fail(w, "failed to scan post: "+err.Error(), 500) return } posts = append(posts, p) } if rows.Err() != nil { a.fail(w, "failed to read all posts: "+rows.Err().Error(), 500) return } data := struct { Posts []*post }{posts} a.ok(w, data) } func main() { // @NOTE: the real connection is not required for tests db, err := sql.Open("mysql", "root@/blog") if err != nil { panic(err) } app := &api{db: db} http.HandleFunc("/posts", app.posts) http.ListenAndServe(":8080", nil) } func (a *api) fail(w http.ResponseWriter, msg string, status int) { w.Header().Set("Content-Type", "application/json") data := struct { Error string }{Error: msg} resp, _ := json.Marshal(data) w.WriteHeader(status) w.Write(resp) } func (a *api) ok(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") resp, err := json.Marshal(data) if err != nil { w.WriteHeader(http.StatusInternalServerError) a.fail(w, "oops something evil has happened", 500) return } w.Write(resp) } go-sqlmock-1.0.0/examples/blog/blog_test.go000066400000000000000000000053601257001316600206430ustar00rootroot00000000000000package main import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "github.com/DATA-DOG/go-sqlmock" ) func (a *api) assertJSON(actual []byte, data interface{}, t *testing.T) { expected, err := json.Marshal(data) if err != nil { t.Fatalf("an error '%s' was not expected when marshaling expected json data", err) } if bytes.Compare(expected, actual) != 0 { t.Errorf("the expected json: %s is different from actual %s", expected, actual) } } func TestShouldGetPosts(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() // create app with mocked db, request and response to test app := &api{db} req, err := http.NewRequest("GET", "http://localhost/posts", nil) if err != nil { t.Fatalf("an error '%s' was not expected while creating request", err) } w := httptest.NewRecorder() // before we actually execute our api function, we need to expect required DB actions rows := sqlmock.NewRows([]string{"id", "title", "body"}). AddRow(1, "post 1", "hello"). AddRow(2, "post 2", "world") mock.ExpectQuery("^SELECT (.+) FROM posts$").WillReturnRows(rows) // now we execute our request app.posts(w, req) if w.Code != 200 { t.Fatalf("expected status code to be 200, but got: %d", w.Code) } data := struct { Posts []*post }{Posts: []*post{ {ID: 1, Title: "post 1", Body: "hello"}, {ID: 2, Title: "post 2", Body: "world"}, }} app.assertJSON(w.Body.Bytes(), data, t) // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } func TestShouldRespondWithErrorOnFailure(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() // create app with mocked db, request and response to test app := &api{db} req, err := http.NewRequest("GET", "http://localhost/posts", nil) if err != nil { t.Fatalf("an error '%s' was not expected while creating request", err) } w := httptest.NewRecorder() // before we actually execute our api function, we need to expect required DB actions mock.ExpectQuery("^SELECT (.+) FROM posts$").WillReturnError(fmt.Errorf("some error")) // now we execute our request app.posts(w, req) if w.Code != 500 { t.Fatalf("expected status code to be 500, but got: %d", w.Code) } data := struct { Error string }{"failed to fetch posts: some error"} app.assertJSON(w.Body.Bytes(), data, t) // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } go-sqlmock-1.0.0/examples/doc.go000066400000000000000000000000211257001316600164700ustar00rootroot00000000000000package examples go-sqlmock-1.0.0/examples/orders/000077500000000000000000000000001257001316600167015ustar00rootroot00000000000000go-sqlmock-1.0.0/examples/orders/orders.go000066400000000000000000000042711257001316600205320ustar00rootroot00000000000000package main import ( "database/sql" "fmt" "log" "github.com/kisielk/sqlstruct" ) const ORDER_PENDING = 0 const ORDER_CANCELLED = 1 type User struct { Id int `sql:"id"` Username string `sql:"username"` Balance float64 `sql:"balance"` } type Order struct { Id int `sql:"id"` Value float64 `sql:"value"` ReservedFee float64 `sql:"reserved_fee"` Status int `sql:"status"` } func cancelOrder(id int, db *sql.DB) (err error) { tx, err := db.Begin() if err != nil { return } var order Order var user User sql := fmt.Sprintf(` SELECT %s, %s FROM orders AS o INNER JOIN users AS u ON o.buyer_id = u.id WHERE o.id = ? FOR UPDATE`, sqlstruct.ColumnsAliased(order, "o"), sqlstruct.ColumnsAliased(user, "u")) // fetch order to cancel rows, err := tx.Query(sql, id) if err != nil { tx.Rollback() return } defer rows.Close() // no rows, nothing to do if !rows.Next() { tx.Rollback() return } // read order err = sqlstruct.ScanAliased(&order, rows, "o") if err != nil { tx.Rollback() return } // ensure order status if order.Status != ORDER_PENDING { tx.Rollback() return } // read user err = sqlstruct.ScanAliased(&user, rows, "u") if err != nil { tx.Rollback() return } rows.Close() // manually close before other prepared statements // refund order value sql = "UPDATE users SET balance = balance + ? WHERE id = ?" refundStmt, err := tx.Prepare(sql) if err != nil { tx.Rollback() return } defer refundStmt.Close() _, err = refundStmt.Exec(order.Value+order.ReservedFee, user.Id) if err != nil { tx.Rollback() return } // update order status order.Status = ORDER_CANCELLED sql = "UPDATE orders SET status = ?, updated = NOW() WHERE id = ?" orderUpdStmt, err := tx.Prepare(sql) if err != nil { tx.Rollback() return } defer orderUpdStmt.Close() _, err = orderUpdStmt.Exec(order.Status, order.Id) if err != nil { tx.Rollback() return } return tx.Commit() } func main() { // @NOTE: the real connection is not required for tests db, err := sql.Open("mysql", "root:@/orders") if err != nil { log.Fatal(err) } defer db.Close() err = cancelOrder(1, db) if err != nil { log.Fatal(err) } } go-sqlmock-1.0.0/examples/orders/orders_test.go000066400000000000000000000071261257001316600215730ustar00rootroot00000000000000package main import ( "fmt" "testing" "github.com/DATA-DOG/go-sqlmock" ) // will test that order with a different status, cannot be cancelled func TestShouldNotCancelOrderWithNonPendingStatus(t *testing.T) { // open database stub db, mock, err := sqlmock.New() if err != nil { t.Errorf("An error '%s' was not expected when opening a stub database connection", err) } defer db.Close() // columns are prefixed with "o" since we used sqlstruct to generate them columns := []string{"o_id", "o_status"} // expect transaction begin mock.ExpectBegin() // expect query to fetch order and user, match it with regexp mock.ExpectQuery("SELECT (.+) FROM orders AS o INNER JOIN users AS u (.+) FOR UPDATE"). WithArgs(1). WillReturnRows(sqlmock.NewRows(columns).FromCSVString("1,1")) // expect transaction rollback, since order status is "cancelled" mock.ExpectRollback() // run the cancel order function err = cancelOrder(1, db) if err != nil { t.Errorf("Expected no error, but got %s instead", err) } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } // will test order cancellation func TestShouldRefundUserWhenOrderIsCancelled(t *testing.T) { // open database stub db, mock, err := sqlmock.New() if err != nil { t.Errorf("An error '%s' was not expected when opening a stub database connection", err) } defer db.Close() // columns are prefixed with "o" since we used sqlstruct to generate them columns := []string{"o_id", "o_status", "o_value", "o_reserved_fee", "u_id", "u_balance"} // expect transaction begin mock.ExpectBegin() // expect query to fetch order and user, match it with regexp mock.ExpectQuery("SELECT (.+) FROM orders AS o INNER JOIN users AS u (.+) FOR UPDATE"). WithArgs(1). WillReturnRows(sqlmock.NewRows(columns).AddRow(1, 0, 25.75, 3.25, 2, 10.00)) // expect user balance update mock.ExpectPrepare("UPDATE users SET balance").ExpectExec(). WithArgs(25.75+3.25, 2). // refund amount, user id WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row // expect order status update mock.ExpectPrepare("UPDATE orders SET status").ExpectExec(). WithArgs(ORDER_CANCELLED, 1). // status, id WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row // expect a transaction commit mock.ExpectCommit() // run the cancel order function err = cancelOrder(1, db) if err != nil { t.Errorf("Expected no error, but got %s instead", err) } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } // will test order cancellation func TestShouldRollbackOnError(t *testing.T) { // open database stub db, mock, err := sqlmock.New() if err != nil { t.Errorf("An error '%s' was not expected when opening a stub database connection", err) } defer db.Close() // expect transaction begin mock.ExpectBegin() // expect query to fetch order and user, match it with regexp mock.ExpectQuery("SELECT (.+) FROM orders AS o INNER JOIN users AS u (.+) FOR UPDATE"). WithArgs(1). WillReturnError(fmt.Errorf("Some error")) // should rollback since error was returned from query execution mock.ExpectRollback() // run the cancel order function err = cancelOrder(1, db) // error should return back if err == nil { t.Error("Expected error, but got none") } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } go-sqlmock-1.0.0/expectations.go000066400000000000000000000230271257001316600166260ustar00rootroot00000000000000package sqlmock import ( "database/sql/driver" "fmt" "reflect" "regexp" "strings" "sync" ) // Argument interface allows to match // any argument in specific way when used with // ExpectedQuery and ExpectedExec expectations. type Argument interface { Match(driver.Value) bool } // an expectation interface type expectation interface { fulfilled() bool Lock() Unlock() String() string } // common expectation struct // satisfies the expectation interface type commonExpectation struct { sync.Mutex triggered bool err error } func (e *commonExpectation) fulfilled() bool { return e.triggered } // ExpectedClose is used to manage *sql.DB.Close expectation // returned by *Sqlmock.ExpectClose. type ExpectedClose struct { commonExpectation } // WillReturnError allows to set an error for *sql.DB.Close action func (e *ExpectedClose) WillReturnError(err error) *ExpectedClose { e.err = err return e } // String returns string representation func (e *ExpectedClose) String() string { msg := "ExpectedClose => expecting database Close" if e.err != nil { msg += fmt.Sprintf(", which should return error: %s", e.err) } return msg } // ExpectedBegin is used to manage *sql.DB.Begin expectation // returned by *Sqlmock.ExpectBegin. type ExpectedBegin struct { commonExpectation } // WillReturnError allows to set an error for *sql.DB.Begin action func (e *ExpectedBegin) WillReturnError(err error) *ExpectedBegin { e.err = err return e } // String returns string representation func (e *ExpectedBegin) String() string { msg := "ExpectedBegin => expecting database transaction Begin" if e.err != nil { msg += fmt.Sprintf(", which should return error: %s", e.err) } return msg } // ExpectedCommit is used to manage *sql.Tx.Commit expectation // returned by *Sqlmock.ExpectCommit. type ExpectedCommit struct { commonExpectation } // WillReturnError allows to set an error for *sql.Tx.Close action func (e *ExpectedCommit) WillReturnError(err error) *ExpectedCommit { e.err = err return e } // String returns string representation func (e *ExpectedCommit) String() string { msg := "ExpectedCommit => expecting transaction Commit" if e.err != nil { msg += fmt.Sprintf(", which should return error: %s", e.err) } return msg } // ExpectedRollback is used to manage *sql.Tx.Rollback expectation // returned by *Sqlmock.ExpectRollback. type ExpectedRollback struct { commonExpectation } // WillReturnError allows to set an error for *sql.Tx.Rollback action func (e *ExpectedRollback) WillReturnError(err error) *ExpectedRollback { e.err = err return e } // String returns string representation func (e *ExpectedRollback) String() string { msg := "ExpectedRollback => expecting transaction Rollback" if e.err != nil { msg += fmt.Sprintf(", which should return error: %s", e.err) } return msg } // ExpectedQuery is used to manage *sql.DB.Query, *dql.DB.QueryRow, *sql.Tx.Query, // *sql.Tx.QueryRow, *sql.Stmt.Query or *sql.Stmt.QueryRow expectations. // Returned by *Sqlmock.ExpectQuery. type ExpectedQuery struct { queryBasedExpectation rows driver.Rows } // WithArgs will match given expected args to actual database query arguments. // if at least one argument does not match, it will return an error. For specific // arguments an sqlmock.Argument interface can be used to match an argument. func (e *ExpectedQuery) WithArgs(args ...driver.Value) *ExpectedQuery { e.args = args return e } // WillReturnError allows to set an error for expected database query func (e *ExpectedQuery) WillReturnError(err error) *ExpectedQuery { e.err = err return e } // WillReturnRows specifies the set of resulting rows that will be returned // by the triggered query func (e *ExpectedQuery) WillReturnRows(rows driver.Rows) *ExpectedQuery { e.rows = rows return e } // String returns string representation func (e *ExpectedQuery) String() string { msg := "ExpectedQuery => expecting Query or QueryRow which:" msg += "\n - matches sql: '" + e.sqlRegex.String() + "'" if len(e.args) == 0 { msg += "\n - is without arguments" } else { msg += "\n - is with arguments:\n" for i, arg := range e.args { msg += fmt.Sprintf(" %d - %+v\n", i, arg) } msg = strings.TrimSpace(msg) } if e.rows != nil { msg += "\n - should return rows:\n" rs, _ := e.rows.(*rows) for i, row := range rs.rows { msg += fmt.Sprintf(" %d - %+v\n", i, row) } msg = strings.TrimSpace(msg) } if e.err != nil { msg += fmt.Sprintf("\n - should return error: %s", e.err) } return msg } // ExpectedExec is used to manage *sql.DB.Exec, *sql.Tx.Exec or *sql.Stmt.Exec expectations. // Returned by *Sqlmock.ExpectExec. type ExpectedExec struct { queryBasedExpectation result driver.Result } // WithArgs will match given expected args to actual database exec operation arguments. // if at least one argument does not match, it will return an error. For specific // arguments an sqlmock.Argument interface can be used to match an argument. func (e *ExpectedExec) WithArgs(args ...driver.Value) *ExpectedExec { e.args = args return e } // WillReturnError allows to set an error for expected database exec action func (e *ExpectedExec) WillReturnError(err error) *ExpectedExec { e.err = err return e } // String returns string representation func (e *ExpectedExec) String() string { msg := "ExpectedExec => expecting Exec which:" msg += "\n - matches sql: '" + e.sqlRegex.String() + "'" if len(e.args) == 0 { msg += "\n - is without arguments" } else { msg += "\n - is with arguments:\n" var margs []string for i, arg := range e.args { margs = append(margs, fmt.Sprintf(" %d - %+v", i, arg)) } msg += strings.Join(margs, "\n") } if e.result != nil { res, _ := e.result.(*result) msg += "\n - should return Result having:" msg += fmt.Sprintf("\n LastInsertId: %d", res.insertID) msg += fmt.Sprintf("\n RowsAffected: %d", res.rowsAffected) if res.err != nil { msg += fmt.Sprintf("\n Error: %s", res.err) } } if e.err != nil { msg += fmt.Sprintf("\n - should return error: %s", e.err) } return msg } // WillReturnResult arranges for an expected Exec() to return a particular // result, there is sqlmock.NewResult(lastInsertID int64, affectedRows int64) method // to build a corresponding result. Or if actions needs to be tested against errors // sqlmock.NewErrorResult(err error) to return a given error. func (e *ExpectedExec) WillReturnResult(result driver.Result) *ExpectedExec { e.result = result return e } // ExpectedPrepare is used to manage *sql.DB.Prepare or *sql.Tx.Prepare expectations. // Returned by *Sqlmock.ExpectPrepare. type ExpectedPrepare struct { commonExpectation mock *sqlmock sqlRegex *regexp.Regexp statement driver.Stmt closeErr error } // WillReturnError allows to set an error for the expected *sql.DB.Prepare or *sql.Tx.Prepare action. func (e *ExpectedPrepare) WillReturnError(err error) *ExpectedPrepare { e.err = err return e } // WillReturnCloseError allows to set an error for this prapared statement Close action func (e *ExpectedPrepare) WillReturnCloseError(err error) *ExpectedPrepare { e.closeErr = err return e } // ExpectQuery allows to expect Query() or QueryRow() on this prepared statement. // this method is convenient in order to prevent duplicating sql query string matching. func (e *ExpectedPrepare) ExpectQuery() *ExpectedQuery { eq := &ExpectedQuery{} eq.sqlRegex = e.sqlRegex e.mock.expected = append(e.mock.expected, eq) return eq } // ExpectExec allows to expect Exec() on this prepared statement. // this method is convenient in order to prevent duplicating sql query string matching. func (e *ExpectedPrepare) ExpectExec() *ExpectedExec { eq := &ExpectedExec{} eq.sqlRegex = e.sqlRegex e.mock.expected = append(e.mock.expected, eq) return eq } // String returns string representation func (e *ExpectedPrepare) String() string { msg := "ExpectedPrepare => expecting Prepare statement which:" msg += "\n - matches sql: '" + e.sqlRegex.String() + "'" if e.err != nil { msg += fmt.Sprintf("\n - should return error: %s", e.err) } if e.closeErr != nil { msg += fmt.Sprintf("\n - should return error on Close: %s", e.closeErr) } return msg } // query based expectation // adds a query matching logic type queryBasedExpectation struct { commonExpectation sqlRegex *regexp.Regexp args []driver.Value } func (e *queryBasedExpectation) attemptMatch(sql string, args []driver.Value) (ret bool) { if !e.queryMatches(sql) { return } defer recover() // ignore panic since we attempt a match if e.argsMatches(args) { return true } return } func (e *queryBasedExpectation) queryMatches(sql string) bool { return e.sqlRegex.MatchString(sql) } func (e *queryBasedExpectation) argsMatches(args []driver.Value) bool { if nil == e.args { return true } if len(args) != len(e.args) { return false } for k, v := range args { matcher, ok := e.args[k].(Argument) if ok { if !matcher.Match(v) { return false } continue } vi := reflect.ValueOf(v) ai := reflect.ValueOf(e.args[k]) switch vi.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if vi.Int() != ai.Int() { return false } case reflect.Float32, reflect.Float64: if vi.Float() != ai.Float() { return false } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if vi.Uint() != ai.Uint() { return false } case reflect.String: if vi.String() != ai.String() { return false } default: // compare types like time.Time based on type only if vi.Kind() != ai.Kind() { return false } } } return true } go-sqlmock-1.0.0/expectations_test.go000066400000000000000000000040671257001316600176700ustar00rootroot00000000000000package sqlmock import ( "database/sql/driver" "fmt" "regexp" "testing" "time" ) type matcher struct { } func (m matcher) Match(driver.Value) bool { return true } func TestQueryExpectationArgComparison(t *testing.T) { e := &queryBasedExpectation{} against := []driver.Value{5} if !e.argsMatches(against) { t.Error("arguments should match, since the no expectation was set") } e.args = []driver.Value{5, "str"} against = []driver.Value{5} if e.argsMatches(against) { t.Error("arguments should not match, since the size is not the same") } against = []driver.Value{3, "str"} if e.argsMatches(against) { t.Error("arguments should not match, since the first argument (int value) is different") } against = []driver.Value{5, "st"} if e.argsMatches(against) { t.Error("arguments should not match, since the second argument (string value) is different") } against = []driver.Value{5, "str"} if !e.argsMatches(against) { t.Error("arguments should match, but it did not") } e.args = []driver.Value{5, time.Now()} const longForm = "Jan 2, 2006 at 3:04pm (MST)" tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)") against = []driver.Value{5, tm} if !e.argsMatches(against) { t.Error("arguments should match (time will be compared only by type), but it did not") } against = []driver.Value{5, matcher{}} if !e.argsMatches(against) { t.Error("arguments should match, but it did not") } } func TestQueryExpectationSqlMatch(t *testing.T) { e := &ExpectedExec{} e.sqlRegex = regexp.MustCompile("SELECT x FROM") if !e.queryMatches("SELECT x FROM someting") { t.Errorf("Sql must have matched the query") } e.sqlRegex = regexp.MustCompile("SELECT COUNT\\(x\\) FROM") if !e.queryMatches("SELECT COUNT(x) FROM someting") { t.Errorf("Sql must have matched the query") } } func ExampleExpectExec() { db, mock, _ := New() result := NewErrorResult(fmt.Errorf("some error")) mock.ExpectExec("^INSERT (.+)").WillReturnResult(result) res, _ := db.Exec("INSERT something") _, err := res.LastInsertId() fmt.Println(err) // Output: some error } go-sqlmock-1.0.0/result.go000066400000000000000000000014661257001316600154410ustar00rootroot00000000000000package sqlmock import ( "database/sql/driver" ) // Result satisfies sql driver Result, which // holds last insert id and rows affected // by Exec queries type result struct { insertID int64 rowsAffected int64 err error } // NewResult creates a new sql driver Result // for Exec based query mocks. func NewResult(lastInsertID int64, rowsAffected int64) driver.Result { return &result{ insertID: lastInsertID, rowsAffected: rowsAffected, } } // NewErrorResult creates a new sql driver Result // which returns an error given for both interface methods func NewErrorResult(err error) driver.Result { return &result{ err: err, } } func (r *result) LastInsertId() (int64, error) { return r.insertID, r.err } func (r *result) RowsAffected() (int64, error) { return r.rowsAffected, r.err } go-sqlmock-1.0.0/result_test.go000066400000000000000000000031101257001316600164640ustar00rootroot00000000000000package sqlmock import ( "fmt" "testing" ) // used for examples var mock = &sqlmock{} func ExampleNewErrorResult() { db, mock, _ := New() result := NewErrorResult(fmt.Errorf("some error")) mock.ExpectExec("^INSERT (.+)").WillReturnResult(result) res, _ := db.Exec("INSERT something") _, err := res.LastInsertId() fmt.Println(err) // Output: some error } func ExampleNewResult() { var lastInsertID, affected int64 result := NewResult(lastInsertID, affected) mock.ExpectExec("^INSERT (.+)").WillReturnResult(result) fmt.Println(mock.ExpectationsWereMet()) // Output: there is a remaining expectation which was not matched: ExpectedExec => expecting Exec which: // - matches sql: '^INSERT (.+)' // - is without arguments // - should return Result having: // LastInsertId: 0 // RowsAffected: 0 } func TestShouldReturnValidSqlDriverResult(t *testing.T) { result := NewResult(1, 2) id, err := result.LastInsertId() if 1 != id { t.Errorf("Expected last insert id to be 1, but got: %d", id) } if err != nil { t.Errorf("expected no error, but got: %s", err) } affected, err := result.RowsAffected() if 2 != affected { t.Errorf("Expected affected rows to be 2, but got: %d", affected) } if err != nil { t.Errorf("expected no error, but got: %s", err) } } func TestShouldReturnErroeSqlDriverResult(t *testing.T) { result := NewErrorResult(fmt.Errorf("some error")) _, err := result.LastInsertId() if err == nil { t.Error("expected error, but got none") } _, err = result.RowsAffected() if err == nil { t.Error("expected error, but got none") } } go-sqlmock-1.0.0/rows.go000066400000000000000000000055271257001316600151170ustar00rootroot00000000000000package sqlmock import ( "database/sql/driver" "encoding/csv" "io" "strings" ) // CSVColumnParser is a function which converts trimmed csv // column string to a []byte representation. currently // transforms NULL to nil var CSVColumnParser = func(s string) []byte { switch { case strings.ToLower(s) == "null": return nil } return []byte(s) } // Rows interface allows to construct rows // which also satisfies database/sql/driver.Rows interface type Rows interface { // composed interface, supports sql driver.Rows driver.Rows // AddRow composed from database driver.Value slice // return the same instance to perform subsequent actions. // Note that the number of values must match the number // of columns AddRow(columns ...driver.Value) Rows // FromCSVString build rows from csv string. // return the same instance to perform subsequent actions. // Note that the number of values must match the number // of columns FromCSVString(s string) Rows // RowError allows to set an error // which will be returned when a given // row number is read RowError(row int, err error) Rows // CloseError allows to set an error // which will be returned by rows.Close // function. // // The close error will be triggered only in cases // when rows.Next() EOF was not yet reached, that is // a default sql library behavior CloseError(err error) Rows } type rows struct { cols []string rows [][]driver.Value pos int nextErr map[int]error closeErr error } func (r *rows) Columns() []string { return r.cols } func (r *rows) Close() error { return r.closeErr } // advances to next row func (r *rows) Next(dest []driver.Value) error { r.pos++ if r.pos > len(r.rows) { return io.EOF // per interface spec } for i, col := range r.rows[r.pos-1] { dest[i] = col } return r.nextErr[r.pos-1] } // NewRows allows Rows to be created from a // sql driver.Value slice or from the CSV string and // to be used as sql driver.Rows func NewRows(columns []string) Rows { return &rows{cols: columns, nextErr: make(map[int]error)} } func (r *rows) CloseError(err error) Rows { r.closeErr = err return r } func (r *rows) RowError(row int, err error) Rows { r.nextErr[row] = err return r } func (r *rows) AddRow(values ...driver.Value) Rows { if len(values) != len(r.cols) { panic("Expected number of values to match number of columns") } row := make([]driver.Value, len(r.cols)) for i, v := range values { row[i] = v } r.rows = append(r.rows, row) return r } func (r *rows) FromCSVString(s string) Rows { res := strings.NewReader(strings.TrimSpace(s)) csvReader := csv.NewReader(res) for { res, err := csvReader.Read() if err != nil || res == nil { break } row := make([]driver.Value, len(r.cols)) for i, v := range res { row[i] = CSVColumnParser(strings.TrimSpace(v)) } r.rows = append(r.rows, row) } return r } go-sqlmock-1.0.0/rows_test.go000066400000000000000000000127641257001316600161570ustar00rootroot00000000000000package sqlmock import ( "database/sql" "fmt" "testing" ) func ExampleRows() { db, mock, err := New() if err != nil { fmt.Println("failed to open sqlmock database:", err) } defer db.Close() rows := NewRows([]string{"id", "title"}). AddRow(1, "one"). AddRow(2, "two") mock.ExpectQuery("SELECT").WillReturnRows(rows) rs, _ := db.Query("SELECT") defer rs.Close() for rs.Next() { var id int var title string rs.Scan(&id, &title) fmt.Println("scanned id:", id, "and title:", title) } if rs.Err() != nil { fmt.Println("got rows error:", rs.Err()) } // Output: scanned id: 1 and title: one // scanned id: 2 and title: two } func ExampleRows_rowError() { db, mock, err := New() if err != nil { fmt.Println("failed to open sqlmock database:", err) } defer db.Close() rows := NewRows([]string{"id", "title"}). AddRow(0, "one"). AddRow(1, "two"). RowError(1, fmt.Errorf("row error")) mock.ExpectQuery("SELECT").WillReturnRows(rows) rs, _ := db.Query("SELECT") defer rs.Close() for rs.Next() { var id int var title string rs.Scan(&id, &title) fmt.Println("scanned id:", id, "and title:", title) } if rs.Err() != nil { fmt.Println("got rows error:", rs.Err()) } // Output: scanned id: 0 and title: one // got rows error: row error } func ExampleRows_closeError() { db, mock, err := New() if err != nil { fmt.Println("failed to open sqlmock database:", err) } defer db.Close() rows := NewRows([]string{"id", "title"}).CloseError(fmt.Errorf("close error")) mock.ExpectQuery("SELECT").WillReturnRows(rows) rs, _ := db.Query("SELECT") // Note: that close will return error only before rows EOF // that is a default sql package behavior. If you run rs.Next() // it will handle the error internally and return nil bellow if err := rs.Close(); err != nil { fmt.Println("got error:", err) } // Output: got error: close error } func TestAllowsToSetRowsErrors(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() rows := NewRows([]string{"id", "title"}). AddRow(0, "one"). AddRow(1, "two"). RowError(1, fmt.Errorf("error")) mock.ExpectQuery("SELECT").WillReturnRows(rows) rs, err := db.Query("SELECT") if err != nil { t.Fatalf("unexpected error: %s", err) } defer rs.Close() if !rs.Next() { t.Fatal("expected the first row to be available") } if rs.Err() != nil { t.Fatalf("unexpected error: %s", rs.Err()) } if rs.Next() { t.Fatal("was not expecting the second row, since there should be an error") } if rs.Err() == nil { t.Fatal("expected an error, but got none") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestRowsCloseError(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() rows := NewRows([]string{"id"}).CloseError(fmt.Errorf("close error")) mock.ExpectQuery("SELECT").WillReturnRows(rows) rs, err := db.Query("SELECT") if err != nil { t.Fatalf("unexpected error: %s", err) } if err := rs.Close(); err == nil { t.Fatal("expected a close error") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestQuerySingleRow(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() rows := NewRows([]string{"id"}). AddRow(1). AddRow(2) mock.ExpectQuery("SELECT").WillReturnRows(rows) var id int if err := db.QueryRow("SELECT").Scan(&id); err != nil { t.Fatalf("unexpected error: %s", err) } mock.ExpectQuery("SELECT").WillReturnRows(NewRows([]string{"id"})) if err := db.QueryRow("SELECT").Scan(&id); err != sql.ErrNoRows { t.Fatal("expected sql no rows error") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestRowsScanError(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() r := NewRows([]string{"col1", "col2"}).AddRow("one", "two").AddRow("one", nil) mock.ExpectQuery("SELECT").WillReturnRows(r) rs, err := db.Query("SELECT") if err != nil { t.Fatalf("unexpected error: %s", err) } defer rs.Close() var one, two string if !rs.Next() || rs.Err() != nil || rs.Scan(&one, &two) != nil { t.Fatal("unexpected error on first row scan") } if !rs.Next() || rs.Err() != nil { t.Fatal("unexpected error on second row read") } err = rs.Scan(&one, &two) if err == nil { t.Fatal("expected an error for scan, but got none") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestCSVRowParser(t *testing.T) { t.Parallel() rs := NewRows([]string{"col1", "col2"}).FromCSVString("a,NULL") db, mock, err := New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectQuery("SELECT").WillReturnRows(rs) rw, err := db.Query("SELECT") if err != nil { t.Fatalf("unexpected error: %s", err) } defer rw.Close() var col1 string var col2 []byte rw.Next() if err = rw.Scan(&col1, &col2); err != nil { t.Fatalf("unexpected error: %s", err) } if col1 != "a" { t.Fatalf("expected col1 to be 'a', but got [%T]:%+v", col1, col1) } if col2 != nil { t.Fatalf("expected col2 to be nil, but got [%T]:%+v", col2, col2) } } go-sqlmock-1.0.0/sqlmock.go000066400000000000000000000303311257001316600155650ustar00rootroot00000000000000/* Package sqlmock provides sql driver connection, which allows to test database interactions by expected calls and simulate their results or errors. It does not require any modifications to your source code in order to test and mock database operations. It does not even require a real database in order to test your application. The driver allows to mock any sql driver method behavior. Concurrent actions are also supported. */ package sqlmock import ( "database/sql/driver" "fmt" "reflect" "regexp" ) // Sqlmock interface serves to create expectations // for any kind of database action in order to mock // and test real database behavior. type Sqlmock interface { // 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 // expectations in the order they were set or not. // // By default it is set to - true. But if you use goroutines // to parallelize your query executation, that option may // be handy. MatchExpectationsInOrder(bool) } type sqlmock struct { ordered bool dsn string opened int drv *mockDriver expected []expectation } func (c *sqlmock) ExpectClose() *ExpectedClose { e := &ExpectedClose{} c.expected = append(c.expected, e) return e } func (c *sqlmock) MatchExpectationsInOrder(b bool) { c.ordered = b } // 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) } var expected *ExpectedClose var fulfilled int var ok bool for _, next := range c.expected { next.Lock() if next.fulfilled() { next.Unlock() fulfilled++ continue } if expected, ok = next.(*ExpectedClose); ok { break } next.Unlock() if c.ordered { return fmt.Errorf("call to database Close, was not expected, next expectation is: %s", next) } } if expected == nil { msg := "call to database Close was not expected" if fulfilled == len(c.expected) { msg = "all expectations were already fulfilled, " + msg } return fmt.Errorf(msg) } expected.triggered = true expected.Unlock() return expected.err } func (c *sqlmock) ExpectationsWereMet() error { for _, e := range c.expected { if !e.fulfilled() { return fmt.Errorf("there is a remaining expectation which was not matched: %s", e) } } return nil } // Begin meets http://golang.org/pkg/database/sql/driver/#Conn interface func (c *sqlmock) Begin() (driver.Tx, error) { var expected *ExpectedBegin var ok bool var fulfilled int for _, next := range c.expected { next.Lock() if next.fulfilled() { next.Unlock() fulfilled++ continue } if expected, ok = next.(*ExpectedBegin); ok { break } next.Unlock() if c.ordered { return nil, fmt.Errorf("call to database transaction Begin, was not expected, next expectation is: %s", next) } } if expected == nil { msg := "call to database transaction Begin was not expected" if fulfilled == len(c.expected) { msg = "all expectations were already fulfilled, " + msg } return nil, fmt.Errorf(msg) } expected.triggered = true expected.Unlock() return c, expected.err } 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) { query = stripQuery(query) var expected *ExpectedExec var fulfilled int var ok bool for _, next := range c.expected { next.Lock() if next.fulfilled() { next.Unlock() fulfilled++ continue } if c.ordered { if expected, ok = next.(*ExpectedExec); ok { break } next.Unlock() return nil, fmt.Errorf("call to exec query '%s' with args %+v, was not expected, next expectation is: %s", query, args, next) } if exec, ok := next.(*ExpectedExec); ok { if exec.attemptMatch(query, args) { expected = exec break } } next.Unlock() } if expected == nil { msg := "call to exec '%s' query with args %+v was not expected" if fulfilled == len(c.expected) { msg = "all expectations were already fulfilled, " + msg } return nil, fmt.Errorf(msg, query, args) } defer expected.Unlock() expected.triggered = true // converts panic to error in case of reflect value type mismatch defer func(errp *error, exp *ExpectedExec, q string, a []driver.Value) { if e := recover(); e != nil { if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion msg := "exec query \"%s\", args \"%+v\" failed to match with error \"%s\" expectation: %s" *errp = fmt.Errorf(msg, q, a, se, exp) } else { panic(e) // overwise if unknown error panic } } }(&err, expected, query, args) if !expected.queryMatches(query) { return nil, fmt.Errorf("exec query '%s', does not match regex '%s'", query, expected.sqlRegex.String()) } if !expected.argsMatches(args) { return nil, fmt.Errorf("exec query '%s', args %+v does not match expected %+v", query, args, expected.args) } if expected.err != nil { return nil, expected.err // mocked to return error } if expected.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, expected, expected) } return expected.result, err } 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) { var expected *ExpectedPrepare var fulfilled int var ok bool for _, next := range c.expected { next.Lock() if next.fulfilled() { next.Unlock() fulfilled++ continue } if expected, ok = next.(*ExpectedPrepare); ok { break } next.Unlock() if c.ordered { return nil, fmt.Errorf("call to Prepare stetement with query '%s', was not expected, next expectation is: %s", query, next) } } query = stripQuery(query) if expected == nil { msg := "call to Prepare '%s' query was not expected" if fulfilled == len(c.expected) { msg = "all expectations were already fulfilled, " + msg } return nil, fmt.Errorf(msg, query) } expected.triggered = true expected.Unlock() return &statement{c, query, expected.closeErr}, expected.err } 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) { query = stripQuery(query) var expected *ExpectedQuery var fulfilled int var ok bool for _, next := range c.expected { next.Lock() if next.fulfilled() { next.Unlock() fulfilled++ continue } if c.ordered { if expected, ok = next.(*ExpectedQuery); ok { break } next.Unlock() return nil, fmt.Errorf("call to query '%s' with args %+v, was not expected, next expectation is: %s", query, args, next) } if qr, ok := next.(*ExpectedQuery); ok { if qr.attemptMatch(query, args) { expected = qr break } } next.Unlock() } if expected == nil { msg := "call to query '%s' with args %+v was not expected" if fulfilled == len(c.expected) { msg = "all expectations were already fulfilled, " + msg } return nil, fmt.Errorf(msg, query, args) } defer expected.Unlock() expected.triggered = true // converts panic to error in case of reflect value type mismatch defer func(errp *error, exp *ExpectedQuery, q string, a []driver.Value) { if e := recover(); e != nil { if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion msg := "query \"%s\", args \"%+v\" failed to match with error \"%s\" expectation: %s" *errp = fmt.Errorf(msg, q, a, se, exp) } else { panic(e) // overwise if unknown error panic } } }(&err, expected, query, args) if !expected.queryMatches(query) { return nil, fmt.Errorf("query '%s', does not match regex [%s]", query, expected.sqlRegex.String()) } if !expected.argsMatches(args) { return nil, fmt.Errorf("query '%s', args %+v does not match expected %+v", query, args, expected.args) } if expected.err != nil { return nil, expected.err // mocked to return error } if expected.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, expected, expected) } return expected.rows, err } func (c *sqlmock) ExpectQuery(sqlRegexStr string) *ExpectedQuery { e := &ExpectedQuery{} e.sqlRegex = regexp.MustCompile(sqlRegexStr) c.expected = append(c.expected, e) return e } func (c *sqlmock) ExpectCommit() *ExpectedCommit { e := &ExpectedCommit{} c.expected = append(c.expected, e) return e } 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 { var expected *ExpectedCommit var fulfilled int var ok bool for _, next := range c.expected { next.Lock() if next.fulfilled() { next.Unlock() fulfilled++ continue } if expected, ok = next.(*ExpectedCommit); ok { break } next.Unlock() if c.ordered { return fmt.Errorf("call to commit transaction, was not expected, next expectation is: %s", next) } } if expected == nil { msg := "call to commit transaction was not expected" if fulfilled == len(c.expected) { msg = "all expectations were already fulfilled, " + msg } return fmt.Errorf(msg) } expected.triggered = true expected.Unlock() return expected.err } // Rollback meets http://golang.org/pkg/database/sql/driver/#Tx func (c *sqlmock) Rollback() error { var expected *ExpectedRollback var fulfilled int var ok bool for _, next := range c.expected { next.Lock() if next.fulfilled() { next.Unlock() fulfilled++ continue } if expected, ok = next.(*ExpectedRollback); ok { break } next.Unlock() if c.ordered { return fmt.Errorf("call to rollback transaction, was not expected, next expectation is: %s", next) } } if expected == nil { msg := "call to rollback transaction was not expected" if fulfilled == len(c.expected) { msg = "all expectations were already fulfilled, " + msg } return fmt.Errorf(msg) } expected.triggered = true expected.Unlock() return expected.err } go-sqlmock-1.0.0/sqlmock_test.go000066400000000000000000000417031257001316600166310ustar00rootroot00000000000000package sqlmock import ( "database/sql" "fmt" "sync" "testing" "time" ) func cancelOrder(db *sql.DB, orderID int) error { tx, _ := db.Begin() _, _ = tx.Query("SELECT * FROM orders {0} FOR UPDATE", orderID) _ = tx.Rollback() return nil } func Example() { // Open new mock database db, mock, err := New() 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 = cancelOrder(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() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectExec("INSERT INTO mytable\\(a, b\\)"). WithArgs("A", "B"). WillReturnResult(NewResult(1, 1)) _, err = db.Exec("INSERT INTO mytable(a, b) VALUES (?, ?)", "A", "B") if err != nil { t.Errorf("error '%s' was not expected, while inserting a row", err) } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } // test the case when db is not triggered and expectations // are not asserted on close func TestIssue4(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectQuery("some sql query which will not be called"). WillReturnRows(NewRows([]string{"id"})) if err := mock.ExpectationsWereMet(); err == nil { t.Errorf("was expecting an error since query was not triggered") } } func TestMockQuery(t *testing.T) { t.Parallel() db, mock, err := New() 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 = ?"). WithArgs(5). WillReturnRows(rs) rows, err := db.Query("SELECT (.+) FROM articles WHERE id = ?", 5) if err != nil { t.Errorf("error '%s' was not expected while retrieving mock rows", err) } defer func() { if er := rows.Close(); er != nil { t.Error("Unexpected error while trying to close rows") } }() if !rows.Next() { t.Error("it must have had one row as result, but got empty result set instead") } var id int var title string err = rows.Scan(&id, &title) if err != nil { t.Errorf("error '%s' was not expected while trying to scan row", err) } if id != 5 { t.Errorf("expected mocked id to be 5, but got %d instead", id) } if title != "hello world" { t.Errorf("expected mocked title to be 'hello world', but got '%s' instead", title) } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } func TestMockQueryTypes(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() columns := []string{"id", "timestamp", "sold"} timestamp := time.Now() rs := NewRows(columns) rs.AddRow(5, timestamp, true) mock.ExpectQuery("SELECT (.+) FROM sales WHERE id = ?"). WithArgs(5). WillReturnRows(rs) rows, err := db.Query("SELECT (.+) FROM sales WHERE id = ?", 5) if err != nil { t.Errorf("error '%s' was not expected while retrieving mock rows", err) } defer func() { if er := rows.Close(); er != nil { t.Error("Unexpected error while trying to close rows") } }() if !rows.Next() { t.Error("it must have had one row as result, but got empty result set instead") } var id int var time time.Time var sold bool err = rows.Scan(&id, &time, &sold) if err != nil { t.Errorf("error '%s' was not expected while trying to scan row", err) } if id != 5 { t.Errorf("expected mocked id to be 5, but got %d instead", id) } if time != timestamp { t.Errorf("expected mocked time to be %s, but got '%s' instead", timestamp, time) } if sold != true { t.Errorf("expected mocked boolean to be true, but got %v instead", sold) } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } func TestTransactionExpectations(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() // begin and commit mock.ExpectBegin() mock.ExpectCommit() tx, err := db.Begin() if err != nil { t.Errorf("an error '%s' was not expected when beginning a transaction", err) } err = tx.Commit() if err != nil { t.Errorf("an error '%s' was not expected when commiting a transaction", err) } // begin and rollback mock.ExpectBegin() mock.ExpectRollback() tx, err = db.Begin() if err != nil { t.Errorf("an error '%s' was not expected when beginning a transaction", err) } err = tx.Rollback() if err != nil { t.Errorf("an error '%s' was not expected when rolling back a transaction", err) } // begin with an error mock.ExpectBegin().WillReturnError(fmt.Errorf("some err")) tx, err = db.Begin() if err == nil { t.Error("an error was expected when beginning a transaction, but got none") } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } func TestPrepareExpectations(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectPrepare("SELECT (.+) FROM articles WHERE id = ?") stmt, err := db.Prepare("SELECT (.+) FROM articles WHERE id = ?") if err != nil { t.Errorf("error '%s' was not expected while creating a prepared statement", err) } if stmt == nil { t.Errorf("stmt was expected while creating a prepared statement") } // expect something else, w/o ExpectPrepare() var id int var title string rs := NewRows([]string{"id", "title"}).FromCSVString("5,hello world") mock.ExpectQuery("SELECT (.+) FROM articles WHERE id = ?"). WithArgs(5). WillReturnRows(rs) err = stmt.QueryRow(5).Scan(&id, &title) if err != nil { t.Errorf("error '%s' was not expected while retrieving mock rows", err) } mock.ExpectPrepare("SELECT (.+) FROM articles WHERE id = ?"). WillReturnError(fmt.Errorf("Some DB error occurred")) stmt, err = db.Prepare("SELECT id FROM articles WHERE id = ?") if err == nil { t.Error("error was expected while creating a prepared statement") } if stmt != nil { t.Errorf("stmt was not expected while creating a prepared statement returning error") } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } func TestPreparedQueryExecutions(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectPrepare("SELECT (.+) FROM articles WHERE id = ?") rs1 := NewRows([]string{"id", "title"}).FromCSVString("5,hello world") mock.ExpectQuery("SELECT (.+) FROM articles WHERE id = ?"). WithArgs(5). WillReturnRows(rs1) rs2 := NewRows([]string{"id", "title"}).FromCSVString("2,whoop") mock.ExpectQuery("SELECT (.+) FROM articles WHERE id = ?"). WithArgs(2). WillReturnRows(rs2) stmt, err := db.Prepare("SELECT id, title FROM articles WHERE id = ?") if err != nil { t.Errorf("error '%s' was not expected while creating a prepared statement", err) } var id int var title string err = stmt.QueryRow(5).Scan(&id, &title) if err != nil { t.Errorf("error '%s' was not expected querying row from statement and scanning", err) } if id != 5 { t.Errorf("expected mocked id to be 5, but got %d instead", id) } if title != "hello world" { t.Errorf("expected mocked title to be 'hello world', but got '%s' instead", title) } err = stmt.QueryRow(2).Scan(&id, &title) if err != nil { t.Errorf("error '%s' was not expected querying row from statement and scanning", err) } if id != 2 { t.Errorf("expected mocked id to be 2, but got %d instead", id) } if title != "whoop" { t.Errorf("expected mocked title to be 'whoop', but got '%s' instead", title) } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } func TestUnexpectedOperations(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectPrepare("SELECT (.+) FROM articles WHERE id = ?") stmt, err := db.Prepare("SELECT id, title FROM articles WHERE id = ?") if err != nil { t.Errorf("error '%s' was not expected while creating a prepared statement", err) } var id int var title string err = stmt.QueryRow(5).Scan(&id, &title) if err == nil { t.Error("error was expected querying row, since there was no such expectation") } mock.ExpectRollback() if err := mock.ExpectationsWereMet(); err == nil { t.Errorf("was expecting an error since query was not triggered") } } func TestWrongExpectations(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectBegin() rs1 := NewRows([]string{"id", "title"}).FromCSVString("5,hello world") mock.ExpectQuery("SELECT (.+) FROM articles WHERE id = ?"). WithArgs(5). WillReturnRows(rs1) mock.ExpectCommit().WillReturnError(fmt.Errorf("deadlock occured")) mock.ExpectRollback() // won't be triggered var id int var title string err = db.QueryRow("SELECT id, title FROM articles WHERE id = ? FOR UPDATE", 5).Scan(&id, &title) if err == nil { t.Error("error was expected while querying row, since there begin transaction expectation is not fulfilled") } // lets go around and start transaction tx, err := db.Begin() if err != nil { t.Errorf("an error '%s' was not expected when beginning a transaction", err) } err = db.QueryRow("SELECT id, title FROM articles WHERE id = ? FOR UPDATE", 5).Scan(&id, &title) if err != nil { t.Errorf("error '%s' was not expected while querying row, since transaction was started", err) } err = tx.Commit() if err == nil { t.Error("a deadlock error was expected when commiting a transaction", err) } if err := mock.ExpectationsWereMet(); err == nil { t.Errorf("was expecting an error since query was not triggered") } } func TestExecExpectations(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() result := NewResult(1, 1) mock.ExpectExec("^INSERT INTO articles"). WithArgs("hello"). WillReturnResult(result) res, err := db.Exec("INSERT INTO articles (title) VALUES (?)", "hello") if err != nil { t.Errorf("error '%s' was not expected, while inserting a row", err) } id, err := res.LastInsertId() if err != nil { t.Errorf("error '%s' was not expected, while getting a last insert id", err) } affected, err := res.RowsAffected() if err != nil { t.Errorf("error '%s' was not expected, while getting affected rows", err) } if id != 1 { t.Errorf("expected last insert id to be 1, but got %d instead", id) } if affected != 1 { t.Errorf("expected affected rows to be 1, but got %d instead", affected) } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } func TestRowBuilderAndNilTypes(t *testing.T) { t.Parallel() db, mock, err := New() 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", "active", "created", "status"}). AddRow(1, true, time.Now(), 5). AddRow(2, false, nil, nil) mock.ExpectQuery("SELECT (.+) FROM sales").WillReturnRows(rs) rows, err := db.Query("SELECT * FROM sales") if err != nil { t.Errorf("error '%s' was not expected while retrieving mock rows", err) } defer func() { if er := rows.Close(); er != nil { t.Error("Unexpected error while trying to close rows") } }() // NullTime and NullInt are used from stubs_test.go var ( id int active bool created NullTime status NullInt ) if !rows.Next() { t.Error("it must have had row in rows, but got empty result set instead") } err = rows.Scan(&id, &active, &created, &status) if err != nil { t.Errorf("error '%s' was not expected while trying to scan row", err) } if id != 1 { t.Errorf("expected mocked id to be 1, but got %d instead", id) } if !active { t.Errorf("expected 'active' to be 'true', but got '%v' instead", active) } if !created.Valid { t.Errorf("expected 'created' to be valid, but it %+v is not", created) } if !status.Valid { t.Errorf("expected 'status' to be valid, but it %+v is not", status) } if status.Integer != 5 { t.Errorf("expected 'status' to be '5', but got '%d'", status.Integer) } // test second row if !rows.Next() { t.Error("it must have had row in rows, but got empty result set instead") } err = rows.Scan(&id, &active, &created, &status) if err != nil { t.Errorf("error '%s' was not expected while trying to scan row", err) } if id != 2 { t.Errorf("expected mocked id to be 2, but got %d instead", id) } if active { t.Errorf("expected 'active' to be 'false', but got '%v' instead", active) } if created.Valid { t.Errorf("expected 'created' to be invalid, but it %+v is not", created) } if status.Valid { t.Errorf("expected 'status' to be invalid, but it %+v is not", status) } if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } func TestArgumentReflectValueTypeError(t *testing.T) { t.Parallel() db, mock, err := New() 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"}).AddRow(1) mock.ExpectQuery("SELECT (.+) FROM sales").WithArgs(5.5).WillReturnRows(rs) _, err = db.Query("SELECT * FROM sales WHERE x = ?", 5) if err == nil { t.Error("Expected error, but got none") } } func TestGoroutineExecutionWithUnorderedExpectationMatching(t *testing.T) { t.Parallel() db, mock, err := New() if err != nil { t.Errorf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() // note this line is important for unordered expectation matching mock.MatchExpectationsInOrder(false) result := NewResult(1, 1) mock.ExpectExec("^UPDATE one").WithArgs("one").WillReturnResult(result) mock.ExpectExec("^UPDATE two").WithArgs("one", "two").WillReturnResult(result) mock.ExpectExec("^UPDATE three").WithArgs("one", "two", "three").WillReturnResult(result) var wg sync.WaitGroup queries := map[string][]interface{}{ "one": []interface{}{"one"}, "two": []interface{}{"one", "two"}, "three": []interface{}{"one", "two", "three"}, } wg.Add(len(queries)) for table, args := range queries { go func(tbl string, a []interface{}) { if _, err := db.Exec("UPDATE "+tbl, a...); err != nil { t.Errorf("error was not expected: %s", err) } wg.Done() }(table, args) } wg.Wait() if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expections: %s", err) } } func ExampleSqlmock_goroutines() { db, mock, err := New() if err != nil { fmt.Println("failed to open sqlmock database:", err) } defer db.Close() // note this line is important for unordered expectation matching mock.MatchExpectationsInOrder(false) result := NewResult(1, 1) mock.ExpectExec("^UPDATE one").WithArgs("one").WillReturnResult(result) mock.ExpectExec("^UPDATE two").WithArgs("one", "two").WillReturnResult(result) mock.ExpectExec("^UPDATE three").WithArgs("one", "two", "three").WillReturnResult(result) var wg sync.WaitGroup queries := map[string][]interface{}{ "one": []interface{}{"one"}, "two": []interface{}{"one", "two"}, "three": []interface{}{"one", "two", "three"}, } wg.Add(len(queries)) for table, args := range queries { go func(tbl string, a []interface{}) { if _, err := db.Exec("UPDATE "+tbl, a...); err != nil { fmt.Println("error was not expected:", err) } wg.Done() }(table, args) } wg.Wait() if err := mock.ExpectationsWereMet(); err != nil { fmt.Println("there were unfulfilled expections:", err) } // Output: } go-sqlmock-1.0.0/statement.go000066400000000000000000000007261257001316600161250ustar00rootroot00000000000000package sqlmock import ( "database/sql/driver" ) type statement struct { conn *sqlmock query string err error } func (stmt *statement) Close() error { return stmt.err } func (stmt *statement) NumInput() int { return -1 } func (stmt *statement) Exec(args []driver.Value) (driver.Result, error) { return stmt.conn.Exec(stmt.query, args) } func (stmt *statement) Query(args []driver.Value) (driver.Rows, error) { return stmt.conn.Query(stmt.query, args) } go-sqlmock-1.0.0/stubs_test.go000066400000000000000000000025371257001316600163220ustar00rootroot00000000000000package sqlmock import ( "database/sql/driver" "fmt" "strconv" "time" ) type NullTime struct { Time time.Time Valid bool // Valid is true if Time is not NULL } type NullInt struct { Integer int Valid bool } // Satisfy sql.Scanner interface func (ni *NullInt) Scan(value interface{}) (err error) { if value == nil { ni.Integer, ni.Valid = 0, false return } switch v := value.(type) { case int, int8, int16, int32, int64: ni.Integer, ni.Valid = v.(int), true return case []byte: ni.Integer, err = strconv.Atoi(string(v)) ni.Valid = (err == nil) return case string: ni.Integer, err = strconv.Atoi(v) ni.Valid = (err == nil) return } ni.Valid = false return fmt.Errorf("Can't convert %T to integer", value) } // Satisfy sql.Valuer interface. func (ni NullInt) Value() (driver.Value, error) { if !ni.Valid { return nil, nil } return ni.Integer, nil } // Satisfy sql.Scanner interface func (nt *NullTime) Scan(value interface{}) (err error) { if value == nil { nt.Time, nt.Valid = time.Time{}, false return } switch v := value.(type) { case time.Time: nt.Time, nt.Valid = v, true return } nt.Valid = false return fmt.Errorf("Can't convert %T to time.Time", value) } // Satisfy sql.Valuer interface. func (nt NullTime) Value() (driver.Value, error) { if !nt.Valid { return nil, nil } return nt.Time, nil } go-sqlmock-1.0.0/util.go000066400000000000000000000003361257001316600150730ustar00rootroot00000000000000package sqlmock import ( "regexp" "strings" ) var re = regexp.MustCompile("\\s+") // strip out new lines and trim spaces func stripQuery(q string) (s string) { return strings.TrimSpace(re.ReplaceAllString(q, " ")) } go-sqlmock-1.0.0/util_test.go000066400000000000000000000007061257001316600161330ustar00rootroot00000000000000package sqlmock import ( "testing" ) func TestQueryStringStripping(t *testing.T) { assert := func(actual, expected string) { if res := stripQuery(actual); res != expected { t.Errorf("Expected '%s' to be '%s', but got '%s'", actual, expected, res) } } assert(" SELECT 1", "SELECT 1") assert("SELECT 1 FROM d", "SELECT 1 FROM d") assert(` SELECT c FROM D `, "SELECT c FROM D") assert("UPDATE (.+) SET ", "UPDATE (.+) SET") }