How do you test context-aware code, including timeouts and cancellation?
Question 287MediumGo 1.22 to 1.25
Techniques:
t.Context()(Go 1.24): a per-test context that is canceled just beforet.Cleanupfunctions run. It replacescontext.Background()in tests, and goroutines started by the test see the cancellation when the test ends.- Pre-canceled contexts: check that a function returns immediately and cleanly.
- Short deadlines combined with blocking fakes: check that code gives up and doesn't leak.
testing/synctest(Go 1.25): fake time inside a bubble, so tests of aWithTimeout(ctx, time.Hour)path run instantly and deterministically.go.uber.org/goleakto assert that no goroutines are left behind after cancellation.
func TestFetchHonorsCancel(t *testing.T) {
block := make(chan struct{})
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case <-r.Context().Done(): // server sees the client abort
case <-block:
}
}))
defer srv.Close()
defer close(block)
ctx, cancel := context.WithTimeout(t.Context(), 20*time.Millisecond)
defer cancel()
_, err := fetch(ctx, srv.URL)
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("want DeadlineExceeded, got %v", err)
}
}
Gotcha: assert on the error with errors.Is, not by comparing strings. Avoid time.Sleep-based synchronization; use channels, or synctest.Wait.
More on Context
- Q285How do deadlines propagate across service boundaries (e.g., gRPC), and how do you manage a time budget across multiple downstream calls?
- Q286How do you implement graceful shutdown with signal.NotifyContext, and what context mistake defeats it?
- Q288Is a context safe for concurrent use? What happens if you read ctx.Done() from many goroutines, or call cancel concurrently?
- Q289How should a library expose context in its API, and how do you add context support to an existing API without breaking callers?
- Q290What does this print? (AfterFunc on already-canceled and live contexts)
- Q291What does this print? (Cause across WithoutCancel)