Skip to content

Instantly share code, notes, and snippets.

@jasonkeene
Last active December 24, 2021 03:14
Show Gist options
  • Save jasonkeene/aee33df73a631b7c1d831fd7b0c581c1 to your computer and use it in GitHub Desktop.
Save jasonkeene/aee33df73a631b7c1d831fd7b0c581c1 to your computer and use it in GitHub Desktop.
Golang Test Setup
// Before method that runs before each test and can communicate values via arguments
// Pros: Consolidate setup code
// Pros: No sharing of memory between tests, can run in parallel
// Pros: Use symbols for dependencies
// Cons: A little bit magical with using reflection.
func TestSomething(t *testing.T) {
t.Before(func (t *testing.T) (*db.Conn, map[string]interface{}) {
return createDBConn(t), createFixture()
})
t.Run("some create test", func (t *testing.T, dbConn *db.Conn, fixture map[string]interface{}) {
dbConn.Create(fixture)
})
t.Run("some update test", func (t *testing.T, dbConn *db.Conn, fixture map[string]interface{}) {
dbConn.Update(fixture)
})
t.Run("some delete test", func (t *testing.T, dbConn *db.Conn, fixture map[string]interface{}) {
dbConn.Update(fixture)
})
}
// Before method that runs before each test and can communicate values to the test through state on T
// Pros: Consolidate setup code
// Pros: No sharing of memory between tests, can run in parallel
// Cons: Does not use symbols for dependencies
// Cons: Requires type asserting
func TestSomething(t *testing.T) {
t.Before(func (t *testing.T) {
t.S("dbConn", createDBConn(t))
t.S("fixture", createFixture())
})
t.Run("some create test", func (t *testing.T) {
t.L("dbConn").(*db.Conn).Create(t.L("fixture").(map[string]interface{}))
})
t.Run("some update test", func (t *testing.T) {
t.L("dbConn").(*db.Conn).Update(t.L("fixture").(map[string]interface{}))
})
t.Run("some delete test", func (t *testing.T) {
t.L("dbConn").(*db.Conn).Update(t.L("fixture").(map[string]interface{}))
})
}
// A setup func that mutates closure variables
// Pros: Use symbols for dependencies
// Pros: Consolidate setup code
// Cons: Repeat `setup()` call in each test
// Cons: Closure variables share memory between tests which prevents running in parallel
func TestSomething(t *testing.T) {
var (
dbConn *db.Conn
fixture map[string]interface{}
)
setup := func () {
dbConn = createDBConn(t)
fixture = createFixture()
}
t.Run("some create test", func (t *testing.T) {
setup()
dbConn.Create(fixture)
})
t.Run("some update test", func (t *testing.T) {
setup()
dbConn.Update(fixture)
})
t.Run("some delete test", func (t *testing.T) {
setup()
dbConn.Delete(fixture)
})
}
// Have all setup code invoked from each test
// Pros: No sharing of memory between tests, can run in parallel
// Pros: Use symbols for dependencies
// Cons: Repeat setup code in each test
func TestSomething(t *testing.T) {
t.Run("some create test", func (t *testing.T) {
dbConn := createDBConn(t)
fixture := createFixture()
dbConn.Create(fixture)
})
t.Run("some update test", func (t *testing.T) {
dbConn := createDBConn(t)
fixture := createFixture()
dbConn.Update(fixture)
})
t.Run("some delete test", func (t *testing.T) {
dbConn := createDBConn(t)
fixture := createFixture()
dbConn.Delete(fixture)
})
}
// Table tests can be used to reduce setup boilerplate
// Pros: Consolidate setup code
// Pros: No sharing of memory between tests, can run in parallel
// Pros: Use symbols for dependencies
// Cons: Tests become generic and hard to understand
func TestSomething(t *testing.T) {
testBodies := map[string]func(dbConn *db.Conn, fixture map[string]interface{}){
"create": func(dbConn *db.Conn, fixture map[string]interface{}) {
dbConn.Create(fixture)
},
"update": func(dbConn *db.Conn, fixture map[string]interface{}) {
dbConn.Update(fixture)
},
"delete": func(dbConn *db.Conn, fixture map[string]interface{}) {
dbConn.Update(fixture)
},
}
for tn, tb := range testBodies {
dbConn := createDBConn(t)
fixture := createFixture()
t.Run(fmt.Sprintf("some %s test", tn), func (t *testing.T) {
tb(dbConn, fixture)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment