diff --git a/README.md b/README.md index d4151f4..73d2cb3 100644 --- a/README.md +++ b/README.md @@ -1,265 +1,115 @@ -[![Build Status](https://travis-ci.org/DATA-DOG/go-sqlmock.svg)](https://travis-ci.org/DATA-DOG/go-sqlmock) -[![GoDoc](https://godoc.org/github.com/DATA-DOG/go-sqlmock?status.svg)](https://godoc.org/github.com/DATA-DOG/go-sqlmock) -[![Go Report Card](https://goreportcard.com/badge/github.com/DATA-DOG/go-sqlmock)](https://goreportcard.com/report/github.com/DATA-DOG/go-sqlmock) -[![codecov.io](https://codecov.io/github/DATA-DOG/go-sqlmock/branch/master/graph/badge.svg)](https://codecov.io/github/DATA-DOG/go-sqlmock) +# SQL driver mock for Golang (with jmoiron/sqlx support) -# Sql driver mock for Golang +## Forked from [DATA-DOG/go-sqlmock](https://github.com/DATA-DOG/go-sqlmock) -**sqlmock** is a mock library implementing [sql/driver](https://godoc.org/database/sql/driver). Which has one and only -purpose - to simulate any **sql** driver behavior in tests, without needing a real database connection. It helps to -maintain correct **TDD** workflow. - -- this library is now complete and stable. (you may not find new changes for this reason) -- supports concurrency and multiple connections. -- supports **go1.8** Context related feature mocking and Named sql parameters. -- does not require any modifications to your source code. -- the driver allows to mock any sql driver method behavior. -- has strict by default expectation order matching. -- has no third party dependencies. - -**NOTE:** in **v1.2.0** **sqlmock.Rows** has changed to struct from interface, if you were using any type references to that -interface, you will need to switch it to a pointer struct type. Also, **sqlmock.Rows** were used to implement **driver.Rows** -interface, which was not required or useful for mocking and was removed. Hope it will not cause issues. - -## Looking for maintainers - -I do not have much spare time for this library and willing to transfer the repository ownership -to person or an organization motivated to maintain it. Open up a conversation if you are interested. See #230. +### Added functionality +- `Newx() and NewxWithDNS()` which returns `*sqlx.DB` object instead of `*sql.DB` ## Install - go get github.com/DATA-DOG/go-sqlmock + https://github.com/zhashkevych/go-sqlxmock@master -## Documentation and Examples +## Usage Example -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, assuming you use the [go-mysql-driver](https://github.com/go-sql-driver/mysql) - -``` go -package main - -import ( - "database/sql" - - _ "github.com/go-sql-driver/mysql" -) - -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 +Repository Implementation: +```go +type UserRepository interface { + Insert(user domain.User) (int, error) + GetById(id int) (domain.User, error) + Get(username, password string) (domain.User, error) } -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) +type UserRepository struct { + db *sqlx.DB +} + +func NewUserRepository(db *sqlx.DB) *UserRepository { + return &UserRepository{db: db} +} + +func (r *UserRepository) Insert(user domain.User) (int, error) { + var id int + row := r.db.QueryRow("INSERT INTO users (first_name, last_name, username, password) VALUES ($1, $2, $3, $4) RETURNING id", + user.FirstName, user.LastName, user.Username, user.Password) + if err := row.Scan(&id); err != nil { + return 0, err } + + return id, nil } ``` -### Tests with sqlmock - -``` go -package main +Unit Tests: +```go import ( - "fmt" + sqlxmock "github.com/zhashkevych/go-sqlxmock" "testing" - - "github.com/DATA-DOG/go-sqlmock" ) -// a successful case -func TestShouldUpdateStats(t *testing.T) { - db, mock, err := sqlmock.New() +func TestUserRepository_Insert(t *testing.T) { + db, mock, err := sqlxmock.Newx() 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() + s := NewUserRepository(db) - // now we execute our method - if err = recordStats(db, 2, 3); err != nil { - t.Errorf("error was not expected while updating stats: %s", err) + tests := []struct { + name string + s repository.UserRepository + user domain.User + mock func() + want int + wantErr bool + }{ + { + //When everything works as expected + name: "OK", + s: s, + user: domain.User{ + FirstName: "first_name", + LastName: "last_name", + Username: "username", + Password: "password", + }, + mock: func() { + rows := sqlxmock.NewRows([]string{"id"}).AddRow(1) + mock.ExpectQuery("INSERT INTO users").WithArgs("first_name", "last_name", "username", "password").WillReturnRows(rows) + }, + want: 1, + }, + { + name: "Empty Fields", + s: s, + user: domain.User{ + FirstName: "", + LastName: "", + Username: "username", + Password: "password", + }, + mock: func() { + rows := sqlxmock.NewRows([]string{"id"}) + mock.ExpectQuery("INSERT INTO users").WithArgs("first_name", "last_name", "username", "password").WillReturnRows(rows) + }, + wantErr: true, + }, } - // we make sure that all expectations were met - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("there were unfulfilled expectations: %s", err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.mock() + got, err := tt.s.Insert(tt.user) + if (err != nil) != tt.wantErr { + t.Errorf("Get() error new = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil && got != tt.want { + t.Errorf("Get() = %v, want %v", got, tt.want) + } + }) } } - -// 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 expectations: %s", err) - } -} -``` - -## Customize SQL query matching - -There were plenty of requests from users regarding SQL query string validation or different matching option. -We have now implemented the `QueryMatcher` interface, which can be passed through an option when calling -`sqlmock.New` or `sqlmock.NewWithDSN`. - -This now allows to include some library, which would allow for example to parse and validate `mysql` SQL AST. -And create a custom QueryMatcher in order to validate SQL in sophisticated ways. - -By default, **sqlmock** is preserving backward compatibility and default query matcher is `sqlmock.QueryMatcherRegexp` -which uses expected SQL string as a regular expression to match incoming query string. There is an equality matcher: -`QueryMatcherEqual` which will do a full case sensitive match. - -In order to customize the QueryMatcher, use the following: - -``` go - db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) -``` - -The query matcher can be fully customized based on user needs. **sqlmock** will not -provide a standard sql parsing matchers, since various drivers may not follow the same SQL standard. - -## Matching arguments like time.Time - -There may be arguments which are of `struct` type and cannot be compared easily by value like `time.Time`. In this case -**sqlmock** provides an [Argument](https://godoc.org/github.com/DATA-DOG/go-sqlmock#Argument) interface which -can be used in more sophisticated matching. Here is a simple example of time argument matching: - -``` go -type AnyTime struct{} - -// Match satisfies sqlmock.Argument interface -func (a AnyTime) Match(v driver.Value) bool { - _, ok := v.(time.Time) - return ok -} - -func TestAnyTimeArgument(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 users"). - WithArgs("john", AnyTime{}). - WillReturnResult(NewResult(1, 1)) - - _, err = db.Exec("INSERT INTO users(name, created_at) VALUES (?, ?)", "john", time.Now()) - 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 expectations: %s", err) - } -} -``` - -It only asserts that argument is of `time.Time` type. - -## Run tests - - go test -race - -## Change Log - -- **2019-04-06** - added functionality to mock a sql MetaData request -- **2019-02-13** - added `go.mod` removed the references and suggestions using `gopkg.in`. -- **2018-12-11** - added expectation of Rows to be closed, while mocking expected query. -- **2018-12-11** - introduced an option to provide **QueryMatcher** in order to customize SQL query matching. -- **2017-09-01** - it is now possible to expect that prepared statement will be closed, - using **ExpectedPrepare.WillBeClosed**. -- **2017-02-09** - implemented support for **go1.8** features. **Rows** interface was changed to struct - but contains all methods as before and should maintain backwards compatibility. **ExpectedQuery.WillReturnRows** may now - accept multiple row sets. -- **2016-11-02** - `db.Prepare()` was not validating expected prepare SQL - query. It should still be validated even if Exec or Query is not - executed on that prepared statement. -- **2016-02-23** - added **sqlmock.AnyArg()** function to provide any kind - of argument matcher. -- **2016-02-23** - convert expected arguments to driver.Value as natural - driver does, the change may affect time.Time comparison and will be - stricter. See [issue](https://github.com/DATA-DOG/go-sqlmock/issues/31). -- **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) - +``` \ No newline at end of file