Compare t.Cleanup, defer, and TestMain for setup/teardown. When is each appropriate?
Question 381MediumGo 1.22 to 1.25
- defer: fine in a test without parallel subtests; runs when the function returns.
- t.Cleanup(fn): runs after the test and all its subtests complete, in LIFO order. Crucially, helpers can register cleanup so callers don't need to remember — the pattern behind
t.TempDir(). - TestMain(m): per-package, once. For expensive shared resources (start a Docker container, compile a binary, parse flags).
func newTestServer(t *testing.T) *httptest.Server {
t.Helper()
srv := httptest.NewServer(newRouter())
t.Cleanup(srv.Close) // caller can't forget
return srv
}
var pgURL string
func TestMain(m *testing.M) {
flag.Parse()
if testing.Short() {
os.Exit(m.Run()) // skip heavy setup
}
stop, url := startPostgres()
pgURL = url
code := m.Run()
stop() // os.Exit skips defers, so tear down explicitly
os.Exit(code)
}
Gotchas: os.Exit doesn't run deferred functions, so a defer stop() in TestMain followed by os.Exit leaks the resource. Since Go 1.15 you may simply return from TestMain and the exit code is propagated. t.Helper() makes failures report the caller's line, not the helper's.
More on Performance, Profiling & Testing
- Q379What does this test print, and in what order?
- Q380What are the rules and pitfalls of t.Parallel()?
- Q382How does native fuzzing work in Go? Write a fuzz test and explain the corpus.
- Q383What is testing/synctest and what problem does it solve?
- Q384How do you mock dependencies in Go without a mocking framework? Where should the interface live?
- Q385How do you test HTTP handlers and HTTP clients with net/http/httptest?