Documentation
¶
Overview ¶
Package testutil solves repetitive test setup problems that appear across Go codebases: forcing I/O failures on demand, capturing process output, bootstrapping HTTP handlers, and normalizing time-variant values in assertions.
Problem ¶
Unit and integration tests often need infrastructure behavior that is hard to trigger deterministically in production code: `io.Reader` errors, `io.Closer` errors, router wiring for request/response assertions, and snapshot comparisons for payloads that contain changing timestamps. Reimplementing those helpers in every package leads to inconsistent tests and duplicated boilerplate.
testutil centralizes these patterns into small, focused helpers designed for test-only usage.
Key Features ¶
- Deterministic I/O failure mocks: NewErrorReader returns a reader whose ErrorReader.Read always fails; NewErrorCloser returns an io.ReadCloser whose ErrorCloser.Close always fails. These are useful for exercising error paths that are otherwise difficult to trigger.
- HTTP test bootstrap: RouterWithHandler builds an http.Handler backed by `julienschmidt/httprouter` with a route pre-registered, reducing per-test router setup noise.
- Output capture for assertions: CaptureOutput redirects stdout, stderr, and the default logger for the duration of a function call and returns captured output as a string, enabling stable assertions for CLI/log-producing code.
- Time-variant text normalization: ReplaceDateTime and ReplaceUnixTimestamp replace dynamic timestamp fragments in strings (for example JSON responses), making golden/snapshot assertions deterministic.
Benefits ¶
- Less test boilerplate and more consistent test patterns across packages.
- Easier coverage of failure branches and edge cases.
- More stable, less flaky string-based assertions.
Usage ¶
reader := testutil.NewErrorReader("read failed")
closer := testutil.NewErrorCloser("close failed")
_ = reader
_ = closer
output := testutil.CaptureOutput(t, func() {
fmt.Println("hello")
})
h := testutil.RouterWithHandler(http.MethodGet, "/health", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
_ = h
normalized := testutil.ReplaceDateTime(responseBody, "<DATETIME>")
normalized = testutil.ReplaceUnixTimestamp(normalized, "<UNIX_TS>")
_ = output
_ = normalized
These functions are intended for use in tests only.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CaptureOutput ¶
CaptureOutput captures stdout, stderr, and standard-logger output produced while fn runs, and returns it as a single string.
It temporarily redirects the process-global os.Stdout, os.Stderr, and the standard logger (via log.SetOutput) to an in-memory pipe, runs fn, then restores all three — including the logger's previous output destination — before returning. Restoration and cleanup happen on every exit path, including a panic inside fn.
Because it mutates process-global state, CaptureOutput MUST NOT be used from parallel tests (t.Parallel) or while other goroutines write to stdout, stderr, or the standard logger; concurrent use would capture interleaved or missing output.
func ReplaceDateTime ¶
ReplaceDateTime replaces RFC3339-like datetime substrings in src with repl. It is useful for deterministic assertions on JSON responses with dynamic timestamps.
The match is bounded to the RFC3339 grammar (date, "T", time, optional fractional seconds and timezone offset) and never spans newlines. Space-separated datetimes (for example "2006-01-02 15:04:05") are intentionally not matched.
func ReplaceUnixTimestamp ¶
ReplaceUnixTimestamp replaces standalone 19-digit integers in src with repl. It is useful for deterministic assertions on JSON responses with dynamic Unix-nanosecond timestamps.
This is a heuristic keyed only on digit count: any standalone 19-digit integer is replaced, including unrelated identifiers that happen to be exactly 19 digits long. Numbers with more or fewer than 19 digits are left untouched.
func RouterWithHandler ¶
func RouterWithHandler(method, path string, handlerFunc http.HandlerFunc) http.Handler
RouterWithHandler returns a new httprouter with handlerFunc registered for method and path.
Types ¶
type ErrorCloser ¶
ErrorCloser is an io.ReadCloser whose Close method always returns an error. Its Read side yields io.EOF immediately (an empty body); only Close fails. The embedded reader is an io.Reader (not a concrete bytes.Reader), so the exported surface is exactly io.ReadCloser.
func NewErrorCloser ¶
func NewErrorCloser(errMsg string) *ErrorCloser
NewErrorCloser creates an ErrorCloser that returns errMsg from Close.
func (*ErrorCloser) Close ¶
func (e *ErrorCloser) Close() error
Close always returns the configured error.
type ErrorReader ¶
type ErrorReader struct {
// contains filtered or unexported fields
}
ErrorReader is an io.Reader that always returns an error.
func NewErrorReader ¶
func NewErrorReader(msg string) *ErrorReader
NewErrorReader creates an ErrorReader that always returns msg as an error.
type TestHTTPResponseWriter ¶
type TestHTTPResponseWriter interface {
http.ResponseWriter
}
TestHTTPResponseWriter wraps http.ResponseWriter to simplify mock generation.