What is func TestMain(m *testing.M), and when does go test call it instead of running tests?
answer
- one entry point for a whole package
- go test calls it instead of the tests
- nothing runs until you start it yourself
- setup, m.Run, teardown, exit code
- exactly one per test binary
basics
~20 sTestMain is an optional function a test package may declare. The test binary then calls it instead of running the tests directly, and the tests run only when it calls m.Run. It is the hook for package-wide setup and teardown.
solid answer
~50 s`func TestMain(m *testing.M)` is the entry point of a whole test binary. If a test package declares it, the generated test main calls TestMain on the main goroutine instead of running the test functions directly, and nothing runs until TestMain calls `m.Run()`. `m.Run` executes the selected tests, benchmarks, examples and fuzz targets and returns the exit code the process should report, so the standard shape is: do package-wide setup, `code := m.Run()`, tear that setup down, then `os.Exit(code)`. There is exactly one TestMain per test binary, which means one per package across both the internal `package foo` test files and the external `package foo_test` ones. I reach for it when many tests share one expensive resource - in our transport package a single TCP listener is opened once in TestMain rather than per test. Anything that is genuinely per-test stays in the test itself.
code
go · 13 linesvar lis net.Listener
func TestMain(m *testing.M) {
var err error
lis, err = net.Listen("tcp", "127.0.0.1:0")
if err != nil {
fmt.Fprintln(os.Stderr, "listen:", err)
os.Exit(1)
}
code := m.Run()
lis.Close()
os.Exit(code)
}go deeper
Be ready to write the four-line shape from memory: setup, code := m.Run(), teardown, os.Exit(code). Know that without the m.Run call no test in the package runs at all.
Explain that the generated test main calls TestMain instead of the tests, that there is exactly one per test binary across internal and external test files, and that m.Run returns the process exit code.
Show judgment about what deserves to be package-wide: one shared listener or fixture yes, anything per-test no. Mention that a slow TestMain fixture is paid even when -run matches nothing.
Frame it as a cost boundary. Every package with a TestMain fixture pays that startup on every CI shard, so decide deliberately which packages own heavyweight setup and which get a lighter in-process fake.
## What it is `go test` compiles a package's `_test.go` files together with a small generated `main` package. Normally that generated main starts the testing framework, which runs every `TestXxx` function, then exits with a status that tells `go test` whether the package passed. If the package declares a function with exactly this signature: ```go func TestMain(m *testing.M) ``` the generated main calls **that** instead. It hands you a `*testing.M`, a handle representing the whole run. From that moment the framework does nothing on its own: the tests execute only when you call `m.Run()`. That inversion is the entire feature. It gives a package one place to run code **before any test** and **after every test**, on the main goroutine, without repeating it in each test function. ## The canonical shape ```go func TestMain(m *testing.M) { // setup that every test in this package needs code := m.Run() // teardown os.Exit(code) } ``` Three things are load-bearing: 1. **`m.Run()` must actually be called.** If you forget, no test in the package runs, and because nothing failed, `go test` prints `ok` for the package. A suite can go silently dead this way. 2. **`m.Run` returns an `int`** - the process exit code, zero when everything selected passed. `go test` decides the package's verdict from the test binary's exit status, so that value has to reach the outside world, either through `os.Exit(code)` or by simply returning from TestMain and letting the generated main exit with the code `m.Run` recorded. 3. **Teardown goes between `m.Run` and `os.Exit`**, not in a `defer`. `os.Exit` terminates the process immediately and runs no deferred function, so `defer lis.Close()` at the top of TestMain never fires. ## What belongs in it TestMain earns its place when a resource is genuinely shared by the whole package and is expensive or exclusive: - one TCP listener bound for an RPC transport suite, so every test dials the same address; - one connection to a database or a fake backend, opened once; - one temporary directory tree or generated fixture set; - reading a custom command-line flag (via `flag.Parse`) that configures all of the above; - global process state a package's tests need, such as a fixed time zone or a deterministic environment variable. It is the wrong home for anything a single test needs, and for anything that must be undone between tests: TestMain runs once, so state it mutates is visible to every test, including tests running in parallel. ## Scope and identity - **One per test binary.** A package's internal test files (`package foo`) and its external ones (`package foo_test`) compile into a single binary, so declaring TestMain in both is a duplicate-symbol error at build time. - **Per package, not per module.** `go test ./...` builds and runs one binary per package, so each package's TestMain runs in its own process. There is no project-wide hook. - **Main goroutine.** TestMain runs on the goroutine that started the process, which matters for APIs that insist on it. - **The name is reserved.** Because `TestMain` matches the `TestXxx` shape but takes `*testing.M` rather than `*testing.T`, you cannot also have a normal test called `TestMain`. ## What `m.Run` covers `m.Run` honours the flags the run was started with: `-run`, `-bench`, `-fuzz`, `-short`, `-v` and the rest are already parsed (or parsed by `m.Run` itself if TestMain has not called `flag.Parse`). So a TestMain that spins up a heavyweight fixture will do so even for `go test -run NoSuchTest`, which matches nothing - a real cost worth guarding when the fixture is slow. ## Mental model Think of the test binary as an ordinary program whose `main` you are allowed to override. Without TestMain, `main` is "run the tests and exit". With it, `main` is "call the author's function", and the author is now responsible for starting the tests and for reporting their result.
- What happens if a package declares TestMain but never calls m.Run?No test in that package executes. Nothing failed, so the binary exits zero and `go test` prints `ok` for the package - a suite that has quietly stopped testing anything while the build stays green. It is one of the few ways to lose an entire package's coverage without a single error message.
- Can both a package's internal and external test files declare TestMain?No. `package foo` and `package foo_test` test files compile into one test binary, so two TestMain declarations collide at build time. There is exactly one per package, and it covers the tests from both files.
- Does TestMain run once for the whole module when you run go test ./...?No. `go test ./...` builds and runs a separate test binary per package, each in its own process, so each package's TestMain runs independently. There is no module-wide hook, which is why cross-package fixtures usually end up in a shared helper package that each TestMain calls.
It is like being handed the keys to main(): the framework stops driving and waits for you to say go.
saying these in an interview costs you the question
- Thinks TestMain runs before every test function
- Believes tests run automatically even without m.Run
- Expects one TestMain for the whole module or repository
- Puts per-test setup in TestMain and shares mutable state
- Confuses TestMain with an ordinary test taking *testing.T