1

It’s already the 3rd day I’m thinking about this problem but without any solution. I need to unit test my Tx manager - check that in all combinations rollback() and commit() methods all called in proper sequence.

sql.DB has the method BeginTx which returns sql.Tx. It returns a final type, not an interface (according to Go - pass interfaces and return types). But in this case it’s not possible to return mock from BeginTx call - because even if we return mocked Tx-interface it has a different type!

I tested it in some hacky way - I just changed sql.DB mock to return Tx interface instead of final type, and tested against it, but now it needs to integrate test into production code.

I don’t want to make some wrappers on sql.DB in production code just to make unit tests working, but what are other solutions???

And why is it a standard in Go to return types instead of interfaces?
It makes testing harder!

greybeard
  • 2,249
  • 8
  • 30
  • 66
sadensmol
  • 93
  • 1
  • 7
  • At the moment I see 3 possible solutions here: 1. Create a wrapper over sql.DB `type myDB struct { db *sql.DB }` and change all original calls to this wrapper. Wrapper is satisfied with the ITX interface. so it's easy to generate mock and do all needed tests. 2. Another option - using something like sqlmock. It's a 3rd party driver designed for mock testing. It already has methods to control transaction calls. 3. I got from Senior Go developer - don't use unit tests here (I'm still not clear about it) - just start real database and check your data actually persisted there. – sadensmol Jun 29 '23 at 06:10

1 Answers1

0

Dependency injection is the way to go.

sql.BeginTx returns a concrete type, but everywhere you pass the transaction, you should depend on a transaction interface rather than the type.

e.g.

type Transaction interface {
    Commit() error
    Exec(query string, args ...any) (sql.Result, error)
}

Now you can inject a mock transaction rather than the real sql.Tx anywhere as long as your mock satisfies the same type. For testing, your mock could then record calls to the transaction which you can verify.

samsmi7h
  • 31
  • 4
  • The main problem here - that I'm ready to depend on the interface versus final type but it's a Go's stdlib code I'm trying to test. sql.DB contains method BeginTx which returns sql.Tx, so it's not possible to use and interfaces here, and provide a mock. I tried to look deeply into reflect. package but with no luck. Figured out 3 possible solutions here. I will explain below. – sadensmol Jun 29 '23 at 06:04
  • It sounds like your problem is that you are calling `BeginTX` inside the method that you need to use it, rather than calling it in an outer function, and then passing it in ? If you do the latter, then you can pass in a mock instead. – samsmi7h Jun 29 '23 at 19:50
  • Yes this is a good catch! It's a kind of transaction manager I wrote. But now I don't see any good way to test it :) – sadensmol Jul 01 '23 at 07:55
  • Do you have a link to the repo perhaps? – samsmi7h Jul 02 '23 at 11:10