How to Detect Goroutine Leaks with goleak in Tests

Use the `goleak` package to automatically detect and report any goroutines that remain running after your test function completes.

Use the goleak package to automatically detect and report any goroutines that remain running after your test function completes. Import the library, call goleak.VerifyNone() at the end of your test (or use goleak.VerifyTestMain for the entire suite), and it will panic if any unexpected goroutines are found.

For individual test functions, the standard pattern is to defer a verification call. This ensures that even if the test fails early, the cleanup check still runs. You must explicitly ignore goroutines that are expected to run indefinitely, such as those listening on a network socket or waiting on a channel, by passing them to goleak.IgnoreTopFunction.

Here is a practical example of verifying a specific test function:

package mypkg

import (
	"testing"
	"time"

	"go.uber.org/goleak"
)

func TestMyWorker(t *testing.T) {
	// Ensure no goroutines are leaked when this test finishes
	defer goleak.VerifyNone(t)

	// Simulate a worker that might leak if not stopped
	done := make(chan struct{})
	go func() {
		// This goroutine should exit when done is closed
		<-done
	}()

	// Simulate work
	time.Sleep(10 * time.Millisecond)
	close(done)
}

For a more robust approach that covers the entire test suite (including TestMain), use goleak.VerifyTestMain. This is particularly useful for detecting leaks in global initialization or package-level setup code.

// main_test.go
package mypkg

import (
	"testing"
	"go.uber.org/goleak"
)

func TestMain(m *testing.M) {
	// Verify no leaks after all tests in the package run
	goleak.VerifyTestMain(m)
}

If you have legitimate background goroutines that must run during tests, you can configure goleak to ignore them. For example, if your test spawns a server that listens on a port, you can ignore the specific function name:

func TestServer(t *testing.T) {
	defer goleak.VerifyNone(t, goleak.IgnoreTopFunction("net/http.(*Server).Serve"))
	
	// Test logic involving a server
}

Common pitfalls include forgetting to close channels or contexts that keep goroutines alive, or failing to stop timers. If goleak reports a leak, check the stack trace it provides; it will show exactly where the leaked goroutine was created. Always ensure your cleanup logic (closing channels, canceling contexts) runs before the test returns.