Sql mock driver for golang to test database interactions

Overview

Build Status GoDoc Go Report Card codecov.io

Sql driver mock for Golang

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.

  • 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.

Install

go get github.com/DATA-DOG/go-sqlmock

Documentation and Examples

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:

Something you may want to test, assuming you use the go-mysql-driver

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
}

func main() {
	// @NOTE: the real connection is not required for tests
	db, err := sql.Open("mysql", "[email protected]/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

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 expectations: %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 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:

	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 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 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.
  • 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
  • 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. 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 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

Issues
  • introduce MockConn type, which represents isolated connections

    introduce MockConn type, which represents isolated connections

    Hi, this is an attempt to isolate connections for parallel testing. Coincidentally I had to solve this issue for myself as well.

    Since the sql.Open() does a whole lot more than just passing a driver.Conn I had to identify instances using IDs and passing them in the DSN.

    Should help with #9

    opened by wongak 18
  • INSERT while mocking gorm

    INSERT while mocking gorm

    I'm having a lot of trouble mocking gorm INSERT queries. I was able to get my tests passing when selecting fine, but I am running into this error when inserting.

    # Gorm's debug output
    INSERT INTO "groups" ("created_at","updated_at","deleted_at","name","description") VALUES ('2018-05-01 17:46:15','2018-05-01 17:46:15',NULL,'Group 1','A good group') RETURNING "groups"."id"
    
    # Error returned from *gorm.DB.Create
    2018/05/01 17:46:15 Error creating group: call to Query 'INSERT INTO "groups" ("created_at","updated_at","deleted_at","name","description") VALUES ($1,$2,$3,$4,$5) RETURNING "groups"."id"' with args [{Name: Ordinal:1 Value:2018-05-01 17:46:15.384319544 -0700 PDT m=+0.005382104} {Name: Ordinal:2 Value:2018-05-01 17:46:15.384319544 -0700 PDT m=+0.005382104} {Name: Ordinal:3 Value:<nil>} {Name: Ordinal:4 Value:Group 1} {Name: Ordinal:5 Value:A good group}], was not expected, next expectation is: ExpectedExec => expecting Exec or ExecContext which:
      - matches sql: '^INSERT INTO "groups" (.+)$'
      - is with arguments:
        0 - {}
        1 - {}
        2 - <nil>
        3 - Group 1
        4 - A good group
      - should return Result having:
          LastInsertId: 1
          RowsAffected: 1
    

    I have tried multiple different versions of the regex, even tested the match using golang on regex101.com, but I can't seem to get my sqlmock to match gorm's insert.

    Here is my test:

    type AnyTime struct{} // I don't actually know if I even need this
    
    func (a AnyTime) Match(v driver.Value) bool {
    	_, ok := v.(time.Time)
    	return ok
    }
    
    func TestGroupService_Create(t *testing.T) {
    	db, mock, err := sqlmock.New()
    	if err != nil {
    		log.Fatalf("can't create sqlmock: %s", err)
    	}
    	models.DB, err = gorm.Open("postgres", db)
    	if err != nil {
    		log.Fatalf("can't open gorm connection: %s", err)
    	}
    	defer db.Close()
    
    	models.DB.LogMode(true)
    
    	name := "Group 1"
    	description := "A good group"
    
    	mock.ExpectExec("^INSERT INTO \"groups\" (.+)$").WithArgs(AnyTime{}, AnyTime{}, nil, name, description).WillReturnResult(sqlmock.NewResult(1, 1))
    
    	s := GroupService{}
    
    	req := &pb.CreateGroupRequest{
    		Name: name,
    		Description: description,
    	}
    
    	resp, err := s.Create(context.Background(), req)
    	assert.Nil(t, err)
    
    	if assert.NotNil(t, resp) {
    		assert.Equal(t, resp.Group.Name, name)
    		assert.Equal(t, resp.Group.Description, description)
    	}
    
    	err = mock.ExpectationsWereMet()
    	assert.Nil(t, err)
    }
    

    and my service method I'm trying to test:

    func (server *GroupService) Create(ctx context.Context, request *pb.CreateGroupRequest) (*pb.CreateGroupReply, error) {
    	var group models.Group
    
    	group.Name = request.Name
    	group.Description = request.Description
    
    	db := models.DB
    
    	if err := db.Create(&group).Error; err != nil {
    		log.Printf("Error creating group: %v", err)
    		return nil, err
    	}
    
    	createReply := pb.CreateGroupReply{
    		Group: mapGroupToService(group),
    	}
    
    	return &createReply, nil
    }
    

    I just can't seem to figure this out. Thanks!

    opened by tfmertz 15
  • No known way to fail on unexpected calls

    No known way to fail on unexpected calls

    I want to make sure the query executed when an expectation is not met is not executed. I looked but there does not seem to be any option to do a sqlMock.ExpectExec().Times(0) or a slMock.FailOnUnexpectedMatches(true) or sqlMock.FailOnExec(). Please let me know/ show me an example on how to work around this. We cannot test an important condition without it

    opened by nazneen84 13
  • v1.3.1 Breaks build

    v1.3.1 Breaks build

    Any idea what is going on here? This has been happening for us since the 1.3.1 release.

    go: finding gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.1
    go: gopkg.in/DATA-DOG/[email protected]: go.mod has non-....v1 module path "sqlmock" at revision v1.3.1
    
    opened by KillerX 12
  • Rows does not implement database/sql/driver.Rows

    Rows does not implement database/sql/driver.Rows

    The new Rows struct is not backward compatible with the previous Rows interface; it's missing all of the driver.Rows methods. T

    he changelog and commit message from #68 leads one to believe that was an inadvertent change.

    opened by bhcleek 12
  • Expectations are not met

    Expectations are not met

    Hi,

    i am mocking a insert statement but the expectation fails, event though i cannot see why. The following error message reveal that the arguments are identical.

    sql_writer_test.go:29: error was not expected while writing event: exec query 'INSERT INTO Event (SourceId, Created, EventType, Version, Payload) VALUES (?, ?, ?, ?, ?)', args [2eb5880e-4bfd-4450-92fc-df851bae5dbb 2016-02-03 22:05:00.712109 +0000 UTC TEST 1 {"version":1,"name":"Joe","balance":12.99,"birth_date":"2015-12-13T23:59:59+02:00"}] does not match expected [2eb5880e-4bfd-4450-92fc-df851bae5dbb 2016-02-03 22:05:00.712109 +0000 UTC TEST 1 {"version":1,"name":"Joe","balance":12.99,"birth_date":"2015-12-13T23:59:59+02:00"}]

    Am i doing something wrong?

    enhancement 
    opened by mantzas 11
  • latest go-sqlmock(v1.3.3) and gorm(v1.9.11) version incompatible

    latest go-sqlmock(v1.3.3) and gorm(v1.9.11) version incompatible

    Use gorm-ut source with upgrade deps version, go.mod:

    module github.com/Rosaniline/gorm-ut
    
    go 1.13
    
    require (
    	github.com/DATA-DOG/go-sqlmock v1.3.3
    	github.com/go-test/deep v1.0.4
    	github.com/jinzhu/gorm v1.9.11
    	github.com/satori/go.uuid v1.2.0
    	github.com/stretchr/testify v1.4.0
    )
    

    and run test, command line shows:

    ?[35m(C:/Users/ping/Desktop/gorm-ut-master/pkg/repository/person.go:24)?[0m
    ?[33m[2019-10-21 15:52:57]?[0m ?[31;1m call to database transaction Begin, was not expected, next expectation is: ExpectedQuery => expecting Query, QueryContext or QueryRow which:
      - matches sql: 'INSERT INTO "person" \("id","name"\)
                            VALUES \(\$1,\$2\) RETURNING "person"\."id"'
      - is with arguments:
        0 - c34a302e-bc5d-4e3d-8104-680ca2a438bb
        1 - test-name
      - should return rows:
        row 0 - [c34a302e-bc5d-4e3d-8104-680ca2a438bb] ?[0m
    
    ?[35m(C:/Users/ping/Desktop/gorm-ut-master/pkg/repository/person.go:30)?[0m
    ?[33m[2019-10-21 15:52:57]?[0m ?[31;1m Query: could not match actual sql: "SELECT * FROM "person" WHERE (id = ?)" with expected regexp "INSERT INTO "person" \("id","name"\) VALUES \(\$
    1,\$2\) RETURNING "person"\."id"" ?[0m
    
    ?[35m(C:/Users/ping/Desktop/gorm-ut-master/pkg/repository/person.go:30)?[0m
    ?[33m[2019-10-21 15:52:57]?[0m  ?[36;1m[0.00ms]?[0m  SELECT * FROM "person"  WHERE (id = 'd5328d68-8664-4aab-ad92-8b74e3010ea8')
    ?[36;31m[0 rows affected or returned ]?[0m
    --- FAIL: TestInit (0.01s)
        --- FAIL: TestInit/Test_repository_Create (0.01s)
            person_test.go:85:
                    Error Trace:    person_test.go:85
                    Error:          Received unexpected error:
                                    call to database transaction Begin, was not expected, next expectation is: ExpectedQuery => expecting Query, QueryContext or QueryRow which:
                                      - matches sql: 'INSERT INTO "person" \("id","name"\)
                                                            VALUES \(\$1,\$2\) RETURNING "person"\."id"'
                                      - is with arguments:
                                        0 - c34a302e-bc5d-4e3d-8104-680ca2a438bb
                                        1 - test-name
                                      - should return rows:
                                        row 0 - [c34a302e-bc5d-4e3d-8104-680ca2a438bb]
                    Test:           TestInit/Test_repository_Create
            person_test.go:45:
                    Error Trace:    person_test.go:45
                                                            suite.go:126
                                                            panic.go:563
                                                            testing.go:653
                                                            person_test.go:85
                    Error:          Received unexpected error:
                                    there is a remaining expectation which was not matched: ExpectedQuery => expecting Query, QueryContext or QueryRow which:
                                      - matches sql: 'INSERT INTO "person" \("id","name"\)
                                                            VALUES \(\$1,\$2\) RETURNING "person"\."id"'
                                      - is with arguments:
                                        0 - c34a302e-bc5d-4e3d-8104-680ca2a438bb
                                        1 - test-name
                                      - should return rows:
                                        row 0 - [c34a302e-bc5d-4e3d-8104-680ca2a438bb]
                    Test:           TestInit/Test_repository_Create
        --- FAIL: TestInit/Test_repository_Get (0.00s)
            person_test.go:66:
                    Error Trace:    person_test.go:66
                    Error:          Received unexpected error:
                                    Query: could not match actual sql: "SELECT * FROM "person" WHERE (id = ?)" with expected regexp "INSERT INTO "person" \("id","name"\) VALUES \(\$1,\$2\)
     RETURNING "person"\."id""
                    Test:           TestInit/Test_repository_Get
            person_test.go:45:
                    Error Trace:    person_test.go:45
                                                            suite.go:126
                                                            panic.go:563
                                                            testing.go:653
                                                            person_test.go:66
                    Error:          Received unexpected error:
                                    there is a remaining expectation which was not matched: ExpectedQuery => expecting Query, QueryContext or QueryRow which:
                                      - matches sql: 'INSERT INTO "person" \("id","name"\)
                                                            VALUES \(\$1,\$2\) RETURNING "person"\."id"'
                                      - is with arguments:
                                        0 - c34a302e-bc5d-4e3d-8104-680ca2a438bb
                                        1 - test-name
                                      - should return rows:
                                        row 0 - [c34a302e-bc5d-4e3d-8104-680ca2a438bb]
                    Test:           TestInit/Test_repository_Get
    FAIL
    FAIL    github.com/Rosaniline/gorm-ut/pkg/repository    0.345s
    FAIL
    

    If not upgrade deps, keep it with origin repo, it works.

    opened by navono 10
  • concurrency support, closes #20 and closes #9 and closes #15

    concurrency support, closes #20 and closes #9 and closes #15

    After some use cases and

    Refactor to support concurrency and multiple connections per mock database instance. When this branch will be merged, there will be BC break and a new major version bump.

    Solves the following issues:

    • [x] parallel tests, any number of mock databases, closes #9 extends and closes #10
    • [x] since rows holds a connection reserved, allow to open any number of connections, closes #20
    • [x] allow to expect database close to be called, closes #15
    • [x] more expectations, for example allow for rows to return error at any point. closes #13
    • [x] deprecated methods: RowsFromCSVString will be removed
    • [x] allow unordered expectation matching, support for goroutines, see test.
    • [x] all possible places where values may be changed, should be available for mocking.
    • [x] more expressive errors
    • [x] documentation updated
    • [x] allow null values from csv string to be converted to nil. closes #22
    opened by l3pp4rd 10
  • Implement ExpectPings to watch for Ping attempts on the database

    Implement ExpectPings to watch for Ping attempts on the database

    Implemented an ExpectPing method to watch for calls to Ping on the driver.

    Some complexity: Ping is used on open to drive internal sql library behaviour to open a connection. We don't want these pings to be treated as expectations so there's a mechanism to override this checking in some cases.

    In order to neatly implement the behaviour without duplicating the Sqlmock interface, the ExpectPing method is installed in the mock in all Go versions, but does nothing in pre-1.8 instances.

    opened by mhuxtable 8
  • Add support for repeatable assertions.

    Add support for repeatable assertions.

    Implements #82.

    The syntax looks like:

    	mock.ExpectQuery("SELECT (.+) FROM articles WHERE id = ?").
    		WithArgs(5).
    		WillReturnRows(rs).
    		AnyNumberOfTimes()
    

    My specific use case is testing some polling APIs that might run any number of times during the test based on exact timing, but this seemed like a generally useful thing to have :-)

    opened by coderanger 8
  • Added join and expectations functions to the sqlmock interface.

    Added join and expectations functions to the sqlmock interface.

    I have added two new functions to the sqlmock interface:

    • Expectations() []expectation
    • Join(sqlmock Sqlmock)

    This means you can now predefine mocks and reuse them as per the next example.

    func MockFindTweeter() (*sql.DB, sqlmock.Sqlmock, error) {
    
    	db, mock, err := sqlmock.New()
    	if err != nil {
    		return nil, nil, err
    	}
    
    	mock.ExpectPrepare("FIND (.+) FROM tweeters").
    		ExpectQuery().
    		WithArgs(tweeterID).
    		WillReturnRows(TweeterRows())
    
    	return db, mock, nil
    }
    
    func MockFindTweets() (*sql.DB, sqlmock.Sqlmock, error) {
    
    	db, mock, err := sqlmock.New()
    	if err != nil {
    		return nil, nil, err
    	}
    
    	mock.ExpectPrepare("FIND (.+) FROM tweets").
    		ExpectQuery().
    		WithArgs(tweeterID).
    		WillReturnRows(TweetRows())
    
    	return db, mock, nil
    }
    
    db, mock, err := database.MockFindTweeter()
    if err != nil {
    	t.Fatal(err)
    }
    
    _, mockFindTweets, err := database.MockFindTweets()
    if err != nil {
    	t.Fatal(err)
    }
    
    mock.Join(mockFindTweets)
    
    

    I would have preferred to have just been able to access sqlmock.expectations directly. Im not sure what the reasoning is for not exporting expectations or the expectations interface.

    opened by bernielomax 8
  • Query with WHERE IN shows an error (using sqlx)

    Query with WHERE IN shows an error (using sqlx)

    Hello sqlmock teams. First of all, thanks to this awesome project. It makes me more easier to implement test code with sql system 👍 But I got an arguments do not match error while running SELECT WHERE IN queries in my code.

    When I run below test codes on sqlmock environment:

    // Test code
    db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
    sqlxDB := sqlx.NewDb(db, "sqlmock")
    wantRows := sqlmock.NewRows([]string{"id", "value"}).
    			AddRow("key1", "value1").
    			AddRow("key2", "value2")
    
    mock.ExpectQuery(fmt.Sprintf("SELECT * FROM %s WHERE id IN (?, ?)", tableName)).
    			WithArgs("key1", "key2").
    			WillReturnRows(wantRows)
    
    GetFeatures(tableName, keys) // keys = ["key1", "key2"]
    
    func GetFeatures(tableName, keys) {
        query, args, err1 := sqlx.In(fmt.Sprintf(`SELECT * FROM %s WHERE id IN (?)`, tableName), keys)
        
        // Processing err1 here
        
        query = sqlxDB.Rebind(query)
        rows, err2 := sqlxDB.Queryx(query, args...)
    
        // Processing err2 here
    }
    
    

    err2 shows an error :

    Query 'SELECT * FROM tableName WHERE id IN (?, ?)', arguments do not match: expected 1, but got 2 arguments
    

    Besides, when I run exact same code with a real MySQL DB server which running locally, it doesn't give any other errors. It runs correctly. I think sqlmock doesn't support SELECT WHERE IN but I'm not sure about it. Is there any way to fix those error?

    Any help will be appreciate. 😄

    question 
    opened by devFallingstar 0
  • Using with goroutines

    Using with goroutines

    Hi, having trouble with using go routines.

    sqlmock version 1.5.0

    below is the code when functions run serial no error occurs but panics when used with goroutines.

    🖊️ update: second script works, which does not use prepared statements

    package main
    
    import (
    	"database/sql"
    	"sync"
    
    	"github.com/DATA-DOG/go-sqlmock"
    )
    
    func main() {
    	db, mock, _ := sqlmock.New()
    
    	mock.MatchExpectationsInOrder(false)
    	data := []interface{}{
    		1,
    		"John Doe",
    		2,
    		"Jane Doe",
    	}
    	rows := sqlmock.NewRows([]string{"id", "name"})
    	rows.AddRow(data[0], data[1])
    	rows.AddRow(data[2], data[3])
    
    	mock.ExpectExec("DROP TABLE IF EXISTS test_out").WillReturnResult(sqlmock.NewResult(0, 0))
    	mock.ExpectExec("TRUNCATE TABLE").WillReturnResult(sqlmock.NewResult(0, 0))
    
    	mock.ExpectExec("CREATE TABLE IF NOT EXISTS test_out").WillReturnResult(sqlmock.NewResult(0, 0))
    
    	mock.ExpectQuery("select").WillReturnRows(rows).WithArgs()
    
    	mock.ExpectPrepare("INSERT INTO").
    		ExpectExec().
    		WithArgs(
    			data[0],
    			data[1],
    			data[2],
    			data[3],
    		).
    		WillReturnResult(sqlmock.NewResult(0, 2))
    
    	funcs := []func(db *sql.DB) error{
    		runSelect,
    		runCreate,
    		runInsert,
    		runDrop,
    		runTruncate,
    	} 
    	// // this works
    	// for _, f := range funcs {
    	// 	if err := f(db); err != nil {
    	// 		panic(err)
    	// 	}
    	// }
    
    	//! this does not work
    	wg := &sync.WaitGroup{}
    	for _, f := range funcs {
    		wg.Add(1)
    		go func(f func(db *sql.DB) error, wg *sync.WaitGroup) {
    			defer wg.Done()
    			if err := f(db); err != nil {
    				panic(err)
    			}
    		}(f, wg)
    	}
    	wg.Wait()
    }
    
    func runSelect(db *sql.DB) error {
    	_, err := db.Query("select * from test_out")
    	return err
    }
    
    func runCreate(db *sql.DB) error {
    	_, err := db.Exec("CREATE TABLE IF NOT EXISTS test_out (a varchar(255)")
    	return err
    }
    
    func runInsert(db *sql.DB) error {
    	stmt, err := db.Prepare("INSERT INTO test_out (id,full_name) VALUES (?,?),(?,?)")
    	if err != nil {
    		return err
    	}
    	_, err = stmt.Exec(1, "John Doe", 2, "Jane Doe")
    	return err
    }
    
    func runDrop(db *sql.DB) error {
    	_, err := db.Exec("DROP TABLE IF EXISTS test_out")
    	return err
    }
    
    func runTruncate(db *sql.DB) error {
    	_, err := db.Exec("TRUNCATE TABLE test_out")
    	return err
    }
    
    package main
    
    import (
    	"database/sql"
    	"sync"
    
    	"github.com/DATA-DOG/go-sqlmock"
    )
    
    func main() {
    	db, mock, _ := sqlmock.New()
    
    	mock.MatchExpectationsInOrder(false)
    	data := []interface{}{
    		1,
    		"John Doe",
    		2,
    		"Jane Doe",
    	}
    	rows := sqlmock.NewRows([]string{"id", "name"})
    	rows.AddRow(data[0], data[1])
    	rows.AddRow(data[2], data[3])
    
    	mock.ExpectExec("DROP TABLE IF EXISTS test_out").WillReturnResult(sqlmock.NewResult(0, 0))
    	mock.ExpectExec("TRUNCATE TABLE").WillReturnResult(sqlmock.NewResult(0, 0))
    
    	mock.ExpectExec("CREATE TABLE IF NOT EXISTS test_out").WillReturnResult(sqlmock.NewResult(0, 0))
    
    	mock.ExpectQuery("select").WillReturnRows(rows).WithArgs()
    
    	// mock.ExpectPrepare("INSERT INTO").
    	// 	ExpectExec().
    	// 	WithArgs(
    	// 		data[0],
    	// 		data[1],
    	// 		data[2],
    	// 		data[3],
    	// 	).
    	// 	WillReturnResult(sqlmock.NewResult(0, 2))
    
    	mock.
    		ExpectExec("INSERT INTO").
    		WithArgs(
    			data[0],
    			data[1],
    			data[2],
    			data[3],
    		).
    		WillReturnResult(sqlmock.NewResult(0, 2))
    
    	funcs := []func(db *sql.DB) error{
    		runSelect,
    		runCreate,
    		runInsert,
    		runDrop,
    		runTruncate,
    	}
    
    	// println("Testing")
    	// for _, f := range funcs {
    	// 	if err := f(db); err != nil {
    	// 		panic(err)
    	// 	}
    	// }
    	println("Testing with go routines")
    	wg := &sync.WaitGroup{}
    	for _, f := range funcs {
    		wg.Add(1)
    		go func(f func(db *sql.DB) error, wg *sync.WaitGroup) {
    			defer wg.Done()
    			if err := f(db); err != nil {
    				panic(err)
    			}
    		}(f, wg)
    	}
    	wg.Wait()
    }
    
    func runSelect(db *sql.DB) error {
    	_, err := db.Query("select * from test_out")
    	return err
    }
    
    func runCreate(db *sql.DB) error {
    	_, err := db.Exec("CREATE TABLE IF NOT EXISTS test_out (a varchar(255)")
    	return err
    }
    
    func runInsert(db *sql.DB) error {
    	_, err := db.Exec("INSERT INTO test_out (id,full_name) VALUES (?,?),(?,?)",
    		1, "John Doe", 2, "Jane Doe",
    	)
    
    	// stmt, err := db.Prepare("INSERT INTO test_out (id,full_name) VALUES (?,?),(?,?)")
    	// if err != nil {
    	// 	return err
    	// }
    	// _, err = stmt.Exec(1, "John Doe", 2, "Jane Doe")
    	return err
    }
    
    func runDrop(db *sql.DB) error {
    	_, err := db.Exec("DROP TABLE IF EXISTS test_out")
    	return err
    }
    
    func runTruncate(db *sql.DB) error {
    	_, err := db.Exec("TRUNCATE TABLE test_out")
    	return err
    }
    
    bug 
    opened by ceyhunkerti 1
  • gorm (v1.22.4) problem

    gorm (v1.22.4) problem

    Hello,

    I am having a problem when I working with gorm v2. I have tried every solution and I could find but none worked.

    gorm version: 1.22.4 sqlmock version: v1.5.0

    This is my code:

    package repositories
    
    import (
    	"balance-service/src/models"
    	"database/sql"
    	"log"
    	"regexp"
    
    	"github.com/DATA-DOG/go-sqlmock"
    	. "github.com/onsi/ginkgo"
    	. "github.com/onsi/gomega"
    	"gorm.io/driver/postgres"
    	"gorm.io/gorm"
    )
    
    var _ = Describe("Repository", func() {
    	var repository *TableTests
    	var mock sqlmock.Sqlmock
    
    	BeforeEach(func() {
    		var db *sql.DB
    		var err error
    
    		db, mock, err = sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
    		Expect(err).ShouldNot(HaveOccurred())
    
    		gdb, err := gorm.Open(postgres.New(postgres.Config{
    			Conn: db,
    		}), &gorm.Config{})
    		Expect(err).ShouldNot(HaveOccurred())
    
    		repository = NewTSRepository(gdb)
    	})
    
    	AfterEach(func() {
    		err := mock.ExpectationsWereMet() // make sure all expectations were met
    		Expect(err).ShouldNot(HaveOccurred())
    	})
    
    	Context("CreateSubscription", func() {
    		var fakeSub *models.TableTest
    
    		BeforeEach(func() {
    			fakeSub = &models.TableTest{
    				ID:   "123",
    				Name: "dudu",
    			}
    		})
    
    		It("save", func() {
    			const query = `
    					INSERT INTO "table_test" ("id","name")
    						VALUES"`
    
    			mock.MatchExpectationsInOrder(false)
    			mock.ExpectBegin()
    			mock.ExpectQuery(regexp.QuoteMeta(query)).
    				WithArgs("123", "dudu").
    				WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
    
    			mock.ExpectCommit()
    
    			log.Println(repository)
    
    			err := repository.CreateTS(fakeSub)
    			Expect(err).ShouldNot(HaveOccurred())
    		})
    	})
    
    })
    

    The error:

    Unexpected error:
          <*fmt.wrapError | 0xc00043fda0>: {
              msg: "call to ExecQuery 'INSERT INTO \"table_tests\" (\"id\",\"name\") VALUES ($1,$2)' with args [{Name: Ordinal:1 Value:123} {Name: Ordinal:2 Value:dudu}] was not expected; call to Rollback transaction was not expected",
              err: <*errors.errorString | 0xc00045dd60>{
                  s: "call to Rollback transaction was not expected",
              },
          }
          call to ExecQuery 'INSERT INTO "table_tests" ("id","name") VALUES ($1,$2)' with args [{Name: Ordinal:1 Value:123} {Name: Ordinal:2 Value:dudu}] was not expected; call to Rollback transaction was not expected
      occurred
    

    Thanks

    bug needs-investigation 
    opened by ehduardu 3
  • Is json tag is not used in this tool

    Is json tag is not used in this tool

    type Temp struct { SourceType stringjson:"SourceKind"} when i mock like this: mockRows := sqlmock.NewRows([]string{"source_kind"}) it's unusefule, bug when i mock like this: mockRows := sqlmock.NewRows([]string{"source_type"}) it's ok.

    bug 
    opened by sevenguin 0
Releases(v1.5.0)
Owner
DATA-DOG
Happy awesome developers
DATA-DOG
CLI tool to mock TCP connections. You can use it with Detox, Cypress or any other framework to automatically mock your backend or database.

Falso It is a CLI that allows you to mock requests/responses between you and any server without any configuration or previous knowledge about how it w

Sorin 3 Mar 15, 2022
A Go implementation of Servirtium, a library that helps test interactions with APIs.

Servirtium is a server that serves as a man-in-the-middle: it processes incoming requests, forwards them to a destination API and writes the response into a Markdown file with a special format that is common across all of the implementations of the library.

Servirtium 6 Jun 16, 2022
Vault mock - Mock of Hashicorp Vault used for unit testing

vault_mock Mock of Hashicorp Vault used for unit testing Notice This is a person

Elliot Rotenstein 0 Jan 19, 2022
Mock-the-fck - Mock exercise for human

Mock the fck Originally, Mockery-example Example case for mockery issue #128 fil

Mike Cat 0 Jan 21, 2022
Record and replay your HTTP interactions for fast, deterministic and accurate tests

go-vcr go-vcr simplifies testing by recording your HTTP interactions and replaying them in future runs in order to provide fast, deterministic and acc

Marin Atanasov Nikolov 884 Jun 24, 2022
Go-interactions - Easy slash commands for Arikawa

go-interactions A library that aims to make dealing with discord's slash command

null 7 May 26, 2022
A tool that integrates SQL, HTTP,interface,Redis mock

Mockit 目标:将mock变得简单,让代码维护变得容易 分支介绍 main 主分支,覆盖了单元测试 light 轻分支,去除了单元测试,简化了依赖项,方便其他团队使用 常见Mock难点 不同中间件,mock库设计模式不一致,学习代价高,差异化明显 mock方案强依赖服务端,无法灵活解耦 单元测试

SHIHUO 14 Apr 19, 2022
Immutable transaction isolated sql driver for golang

Single transaction based sql.Driver for GO Package txdb is a single transaction based database sql driver. When the connection is opened, it starts a

DATA-DOG 450 Jun 21, 2022
Test-assignment - Test assignment with golang

test-assignment We have a two steam of data and we need to save it in the map: I

null 0 Jan 19, 2022
mockery - A mock code autogenerator for Golang

mockery - A mock code autogenerator for Golang

Vektra 3.6k Jun 26, 2022
Just Dance Unlimited mock-up server written on Golang and uses a popular Gin framework for Go.

BDCS Just Dance Unlimited mock-up server written on Golang and uses a popular Gin framework for Go. Features Security Authorization works using UbiSer

Mikhail 0 Nov 10, 2021
A simple mock server configurable via JSON, built using GoLang.

GoMock Server A simple mock server configurable via JSON, built using GoLang. How To A file name endpoint.json must be placed in the context root, wit

Miguel Alexandre 0 Jan 9, 2022
Grpcmock - Mock grpc server with golang

grpcmock Mock gRPC server. Inspired by Prism. Add example responses to your prot

Eric Butler 3 May 8, 2022
go-test-trace is like go test but it also generates distributed traces.

go-test-trace go-test-trace is like go test but it also generates distributed traces. Generated traces are exported in OTLP to a OpenTelemetry collect

JBD 373 Jun 24, 2022
Flugel Test Documentation for steps to run and test the automatio

Flugel Test Documentation Documentation for steps to run and test the automation #Test-01 1 - Local Test Using Terratest (End To End) 1- By runing " t

Suc0_Unix 0 Dec 30, 2021
This repository includes consumer driven contract test for provider, unit test and counter api.

This repository includes consumer driven contract test for provider, unit test and counter api.

Ahmet Zümberoğlu 0 Feb 1, 2022
Powerful mock generation tool for Go programming language

Summary Minimock generates mocks out of Go interface declarations. The main features of minimock are: It generates statically typed mocks and helpers.

Juno Inc. 455 Jun 15, 2022
Mock object for Go http.ResponseWriter

mockhttp -- Go package for unit testing HTTP serving Unit testing HTTP services written in Go means you need to call their ServeHTTP receiver. For thi

Tv 21 Mar 22, 2022
ESME is a go library that allows you to mock a RESTful service by defining the configuration in json format

ESME is a go library that allows you to mock a RESTful service by defining the configuration in json format. This service can then simply be consumed by any client to get the expected response.

Sumit Tokkar 3 Mar 2, 2021