commit | b983233bc0bbea4dc6e8ec8c8c923d935765ffec | [log] [tgz] |
---|---|---|
author | Gediminas Morkevicius <gediminas.morkevicius@gmail.com> | Tue Feb 21 15:59:18 2017 |
committer | GitHub <noreply@github.com> | Tue Feb 21 15:59:18 2017 |
tree | c9059bdc7c1cf21d5b003cf059c6dbffa6be3ac6 | |
parent | 55ecc5a333fae40c70a7b36835c8785268589738 [diff] | |
parent | 372a183d52c500c7299f746e62df4237416f96e9 [diff] |
Merge pull request #68 from DATA-DOG/go1.8 Support for go 1.8 SQL features
sqlmock is a mock library implementing 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.
go get gopkg.in/DATA-DOG/go-sqlmock.v1
Visit godoc 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 all database related actions are isolated within a single transaction so the database can remain in the same state.
See implementation examples:
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) } }
package main import ( "fmt" "testing" "gopkg.in/DATA-DOG/go-sqlmock.v1" ) // 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) } }
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 interface which can be used in more sophisticated matching. Here is a simple example of time argument matching:
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 expections: %s", err) } }
It only asserts that argument is of time.Time
type.
go test -race
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.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