Little DB Test Helper in Go to make test cases transactional
Here is the little helper function that I've been using to make the DB test cases transactional, it's similar to django
's TestCase
which wraps each test case in a transaction, which comes with a few benefits:
- Fast
- There is no need for manual clean up after each test case
Here is the code:
func WithinTransaction(t *testing.T, db *gorm.DB, run func(tx *gorm.DB)) {
tx := db.Begin()
if tx.Error != nil {
t.Fatal("Failed to start transaction")
}
defer tx.Rollback()
run(tx)
}
Practically it's used like this:
func TestSomething(t *testing.T) {
// get the db *db.Gorm as the db connection
WithinTransaction(t, db, func(tx *gorm.DB) {
// your test code here
})
}
This is ideal for uniting test your DB implementation of the repository logic (whether you should unit-test your DB logic or not is a different discussion).
Caveats
If you need to test your HTTP middleware, do make sure that the middleware and your assertion shares the same transaction, otherwise the mutation on the middleware will not be reflected in your DB related assertion.
As the result my HTTP middleware often looks like this:
type Service struct {
DbConn *gorm.DB
//...
}
// WithDBTransaction wraps the request in a transaction
// this is used by every http handler that needs db access
func (svc *Service) WithDBTransaction() gin.HandlerFunc {
return func(c *gin.Context) {
svc.DbConn.Transaction(func(tx *gorm.DB) error {
ctx := c.Request.Context()
ctx = db.TxIntoContext(ctx, tx) // dump the tx into the context
c.Request = c.Request.WithContext(ctx)
c.Next()
return nil
})
}
}
During the tests you can share your test transaction with your service via:
func TestSomething(t *testing.T) {
// get the db *db.Gorm as the db connection
WithinTransaction(t, db, func(tx *gorm.DB) {
// your test code here
svc := &Service{
DbConn: tx,
//...
}
})
}