Testing Code That Accepts context.Context
Functions that accept context.Context need tests for success, deadline exceeded, and manual cancellation - not just the happy path.
Search across all documentation pages
Functions that accept context.Context need tests for success, deadline exceeded, and manual cancellation - not just the happy path.
Go's context package and testing helpers make those paths deterministic without sleeping in CI.
Use context.WithTimeout or WithCancel in tests to drive ctx.Err() outcomes.
Prefer channel-based fakes over time.Sleep when testing cancel propagation.
Assert with errors.Is for context.Canceled and context.DeadlineExceeded.
Quick-reference recipe card - copy-paste ready.
func TestWorkCanceled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := Work(ctx)
if !errors.Is(err, context.Canceled) {
t.Fatalf("got %v, want Canceled", err)
}
}When to reach for this:
ctx outcomes alongside normal inputs.httptest and request contexts.package work_test
import (
"context"
"errors"
"testing"
"time"
"example.com/ctxdemo/work"
)
func TestFetchDeadline(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
defer cancel()
err := work.Fetch(ctx, slowStub{})
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("got %v", err)
}
}
func TestFetchCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(5 * time.Millisecond)
cancel()
}()
err := work.Fetch(ctx, slowStub{})
if !errors.Is(err, context.Canceled) {
t.Fatalf("got %v", err)
}
}
type slowStub struct{}
func (slowStub) Run(ctx context.Context) error {
select {
case <-time.After(200 * time.Millisecond):
return nil
case <-ctx.Done():
return ctx.Err()
}
}What this demonstrates:
DeadlineExceeded from a short WithTimeout without long sleeps in the assertion path.cancel() from another goroutine simulates client disconnect.ctx the same way production I/O would.errors.Is keeps assertions stable if errors wrap with %w.t.Context() (Go 1.24+) provides a context canceled when the test ends - useful for integration tests.httptest.NewRequest attaches a background context; replace with req.WithContext(ctx) for cancel cases.| Scenario | Setup | Expected |
|---|---|---|
| Success | context.Background() | nil error |
| Timeout | WithTimeout(..., 1ms) | DeadlineExceeded |
| Manual cancel | cancel() before call | Canceled |
| Parent canceled | cancel parent of child | child Err() set |
| Cause attached | WithCancelCause | context.Cause(ctx) |
req := httptest.NewRequest(http.MethodGet, "/", nil)
ctx, cancel := context.WithCancel(req.Context())
cancel()
req = req.WithContext(ctx)
rec := httptest.NewRecorder()
handler(rec, req)
// expect no response body on cancelfunc wait(ctx context.Context, d time.Duration) error {
timer := time.NewTimer(d)
defer timer.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
return nil
}
}Always stop timers in tests and production to avoid leaks.
cancel() or 1-20ms timeouts.==. Fix: errors.Is and errors.As.-race runs. Fix: defer cancel() always.WithContext.cancel(). Fix: per-subtest context with t.Parallel() care.| Alternative | Use When | Don't Use When |
|---|---|---|
t.Context() | Auto cleanup on test end | Fine-grained cancel timing control |
| Interface fake with blocking channel | Deterministic cancel | Simple pure functions |
| Integration test with real DB | Driver cancel verification | Unit-level logic tests |
testing/quick | Randomized deadline fuzz | Readable table tests |
time.Sleep in tests | Never preferred | - |
Test functions that branch on ctx.Done() or pass ctx to dependencies.
Pure helpers that only forward ctx can rely on integration tests.
Attach values in test setup with the same key helpers production uses.
Assert accessors return expected metadata.
Yes - assert.ErrorIs(t, err, context.Canceled) reads well in table tests.
Build a context with timeout and invoke the interceptor with httptest or grpc's test buffers.
Verify codes.DeadlineExceeded mapping.
Cancel with WithCancelCause and assert errors.Is(context.Cause(ctx), wantCause).
Use context.Background() unless benchmarking cancel overhead specifically.
Report -benchmem separately from cancel tests.
Always defer cancel() for WithTimeout and stop timers in select loops.
Build a fresh ctx per row inside t.Run to avoid cross-test pollution.
Cancel parent, derive context.WithoutCancel(parent), and assert child still runs briefly.
Verify parent values are visible.
Yes - run cancel tests with -race to catch goroutines that ignore Done().
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026