What is testing/synctest and what problem does it solve?
Question 383HardGo 1.22 to 1.25
Testing code with timeouts, tickers, and background goroutines usually means real sleeps (slow and flaky) or injecting a fake clock everywhere. testing/synctest (experimental in Go 1.24 behind GOEXPERIMENT=synctest as synctest.Run; GA in Go 1.25 as synctest.Test) runs the test in an isolated bubble:
- Goroutines started in the bubble belong to it.
- The bubble has a fake clock (starting at 2000-01-01 UTC). Time advances only when every goroutine in the bubble is durably blocked — then it jumps instantly to the next timer.
synctest.Wait()blocks until all other bubble goroutines are durably blocked — a deterministic "let everything settle."
func TestCacheExpiry(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
c := NewCache(5 * time.Second) // starts a janitor goroutine
defer c.Close()
c.Set("k", "v")
time.Sleep(4 * time.Second) // instant in fake time
synctest.Wait()
if _, ok := c.Get("k"); !ok {
t.Fatal("expired too early")
}
time.Sleep(2 * time.Second)
synctest.Wait()
if _, ok := c.Get("k"); ok {
t.Fatal("should have expired")
}
})
}
Gotchas: blocking on real I/O (network sockets, syscalls) or a mutex is not durable blocking, so use in-memory fakes like net.Pipe; channels created outside the bubble don't count. If all bubble goroutines deadlock, the test fails with a report instead of hanging.
More on Performance, Profiling & Testing
- Q381Compare t.Cleanup, defer, and TestMain for setup/teardown. When is each appropriate?
- Q382How does native fuzzing work in Go? Write a fuzz test and explain the corpus.
- 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?
- Q386How does test coverage work in Go? Explain covermode, -coverpkg, and coverage for integration tests.
- Q387What is Profile-Guided Optimization (PGO) in Go, and how do you use it?