Documentation
¶
Overview ¶
Package testkit provides deterministic, fluent test harnesses for both nexssp actions and standard Go http.Handler implementations.
Layout of this package:
- suite.go — Suite lifecycle: New, NewWithHandler, NewE2E, global headers/cookies, context bridge.
- request.go — Request builder and every With* method; Suite's HTTP verb methods (GET, POST, …).
- response.go — Response and every fluent assertion.
- traverse.go — dot-path resolution used by HasField and ExpectArrayLen.
- chaos_bridge.go — WithChaos, the only bridge from this package into testkit/chaos.
- async.go — Eventually, WaitForJSON.
- bench.go — BenchAction, BenchHTTP.
- concurrency.go — Simulate.
- contract.go — AssertContracts.
- load.go — Suite.LoadTest.
- load_bg.go — Suite.StartBackgroundLoad.
- sse.go, sse_wait.go — Server-Sent Events capture and waits.
- smoke.go — RunSmokeTests.
- script.go — Recorder, Script, MustExecute, MustError.
Subpackages (imported on demand only):
- chaos — stochastic latency/error/panic injection.
- rpc — JSON-RPC 2.0 test client over net.Pipe.
- examples — runnable, self-contained demonstrations of the API.
testkit/trace.go
Index ¶
- Constants
- func AssertContracts(t *testing.T, actions []action.AnyAction)
- func BenchAction[Req, Res any](b *testing.B, act *action.BuiltAction[Req, Res], req Req)
- func BenchHTTP(b *testing.B, s *Suite, method, path string, payload any)
- func Eventually(t testing.TB, timeout, interval time.Duration, condition func() bool)
- func EventuallyE(t testing.TB, timeout, interval time.Duration, fn func() error) error
- func Fake(name string, res any) action.AnyAction
- func FakeErr(name string, err error) action.AnyAction
- func FakeSeq(name string, values ...any) action.AnyAction
- func GoldenJSON(t testing.TB, name string, got any)
- func MustError[Req, Res any](t testing.TB, fn action.Fn[Req, Res], req Req, want error)
- func MustExecute[Req, Res any](t testing.TB, fn action.Fn[Req, Res], req Req) Res
- func RunSmokeTests(t *testing.T, actions []action.AnyAction)
- func Script[Req, Res any](results ...Result[Res]) action.Fn[Req, Res]
- func Simulate[Req, Res any](t testing.TB, act *action.BuiltAction[Req, Res], req Req, concurrency int, ...)
- func WaitForJSON[T any](t testing.TB, timeout, interval time.Duration, ...) T
- func WithChaos(actions []action.AnyAction, cfg chaos.Config) []action.AnyAction
- type Call
- type FakeClock
- type LoadConfig
- type LoadResult
- type Recorder
- func (r *Recorder[Req, Res]) OnCacheHit(context.Context, Req, Res)
- func (r *Recorder[Req, Res]) OnCacheMiss(context.Context, Req)
- func (r *Recorder[Req, Res]) OnCoalesced(context.Context, Req)
- func (r *Recorder[Req, Res]) OnDeduplicated(context.Context, Req)
- func (r *Recorder[Req, Res]) OnRetry(_ context.Context, req Req, attempt int, err error)
- type Request
- func (r *Request) Do() *Response
- func (r *Request) DoContext(ctx context.Context) (*Response, error)
- func (r *Request) DoE() (*Response, error)
- func (r *Request) WithBearerToken(token string) *Request
- func (r *Request) WithContext(ctx context.Context) *Request
- func (r *Request) WithCookie(name, value string) *Request
- func (r *Request) WithForm(data map[string]string) *Request
- func (r *Request) WithHeader(k, v string) *Request
- func (r *Request) WithJSON(v any) *Request
- func (r *Request) WithMultipartFile(fieldName, filename string, content []byte) *Request
- func (r *Request) WithQueries(params map[string]string) *Request
- func (r *Request) WithQuery(key, value string) *Request
- func (r *Request) WithTenant(tenantID string) *Request
- type Response
- func (c *Response) Body() []byte
- func (c *Response) BodyString() string
- func (c *Response) ContainsString(substr string) *Response
- func (c *Response) ExpectArrayLen(path string, n int) *Response
- func (c *Response) ExpectBadRequest() *Response
- func (c *Response) ExpectCreated() *Response
- func (c *Response) ExpectErrorKind(kind xerr.Kind, message string) *Response
- func (c *Response) ExpectForbidden() *Response
- func (c *Response) ExpectHeader(key, value string) *Response
- func (c *Response) ExpectNotFound() *Response
- func (c *Response) ExpectOK() *Response
- func (c *Response) ExpectStatus(code int) *Response
- func (c *Response) ExpectSuccess() *Response
- func (c *Response) ExpectUnauthorized() *Response
- func (c *Response) HasField(path string, expected any) *Response
- func (c *Response) Header(k string) string
- func (c *Response) Into(v any) *Response
- func (c *Response) Status() int
- type Result
- type RetryEvent
- type SSEEvent
- type StreamCapture
- func (sc *StreamCapture) Close()
- func (sc *StreamCapture) Endpoint(t testing.TB, timeout time.Duration) string
- func (sc *StreamCapture) WaitFor(t testing.TB, eventName string, timeout time.Duration) SSEEvent
- func (sc *StreamCapture) WaitForData(t testing.TB, substr string, timeout time.Duration) SSEEvent
- func (sc *StreamCapture) WaitForSSE(t testing.TB, timeout time.Duration, match func(SSEEvent) bool) SSEEvent
- func (sc *StreamCapture) WaitForSSEData(t testing.TB, substr string, timeout time.Duration) SSEEvent
- type Suite
- func (s *Suite) DELETE(path string, body ...any) *Request
- func (s *Suite) GET(path string, body ...any) *Request
- func (s *Suite) ListenSSE(path string) *StreamCapture
- func (s *Suite) ListenSSEWithRequest(t testing.TB, req *http.Request) *StreamCapture
- func (s *Suite) LoadTest(t *testing.T, cfg LoadConfig) LoadResult
- func (s *Suite) PATCH(path string, body ...any) *Request
- func (s *Suite) POST(path string, body ...any) *Request
- func (s *Suite) PUT(path string, body ...any) *Request
- func (s *Suite) Request(method, path string) *Request
- func (s *Suite) ResetHeaders() *Suite
- func (s *Suite) StartBackgroundLoad(cfg LoadConfig) func()
- func (s *Suite) WithCookie(name, value string) *Suite
- func (s *Suite) WithGlobalBearerToken(token string) *Suite
- func (s *Suite) WithGlobalHeader(key, value string) *Suite
- type Trace
- func (t *Trace) AssertAllSucceeded(tb testing.TB)
- func (t *Trace) AssertCalled(tb testing.TB, name string)
- func (t *Trace) AssertErrorsAt(tb testing.TB, want ...string)
- func (t *Trace) AssertNotCalled(tb testing.TB, name string)
- func (t *Trace) AssertOrder(tb testing.TB, first, second string)
- func (t *Trace) AssertSequence(tb testing.TB, want ...string)
- func (t *Trace) AssertSubsequence(tb testing.TB, want ...string)
- func (t *Trace) Calls() []Call
- func (t *Trace) Count(name string) int
- func (t *Trace) Dump(tb testing.TB)
- func (t *Trace) Hook() action.AnyHook
- func (t *Trace) Names() []string
- func (t *Trace) Reset()
Constants ¶
const (
HeaderTenantID = "X-Tenant-ID"
)
Variables ¶
This section is empty.
Functions ¶
func AssertContracts ¶
AssertContracts verifies architectural invariants across all registered actions. Catches duplicate action names, orphaned actions without routes/hooks, and malformed DTOs.
func BenchAction ¶
func BenchAction[Req, Res any](b *testing.B, act *action.BuiltAction[Req, Res], req Req)
BenchAction benchmarks a built action's pure execution speed without transport overhead.
func Eventually ¶ added in v0.3.0
Eventually repeatedly evaluates condition until it returns true or timeout expires. The condition is evaluated immediately before waiting for the first interval. It runs synchronously on the caller's goroutine and Eventually starts no goroutines.
func EventuallyE ¶ added in v0.4.0
EventuallyE repeats fn until it returns nil, or timeout expires.
Unlike Eventually, fn returns an error, and EventuallyE returns the last error observed. This is the right shape for asserting on operations that must complete successfully (commit, flush, connect).
func Fake ¶ added in v0.4.0
Fake returns an action that always succeeds with res. It is the domainless replacement for a hand-written test fixture: every example and every test that needs "an action that just returns X" uses this one function.
func FakeErr ¶ added in v0.4.0
FakeErr returns an action that always fails with err. Use it for "the happy path plus a fallback" tests, contract tests, and any pipeline that must be proven to abort cleanly.
func FakeSeq ¶ added in v0.4.0
FakeSeq returns an action that yields each value in order, then repeats the last one. It is the "flaky on first call, then recovers" helper every retry test needs. Safe for concurrent use.
func GoldenJSON ¶ added in v0.4.0
GoldenJSON compares got against a JSON golden file at testdata/<name>.golden.json. Run with -testkit.update to rewrite it.
The testdata/ directory is created automatically on first run. The comparison is done on the unmarshalled form, so field order and insignificant whitespace do not cause false failures.
func MustExecute ¶
func Simulate ¶
func Simulate[Req, Res any]( t testing.TB, act *action.BuiltAction[Req, Res], req Req, concurrency int, assertFn func(t testing.TB, res Res, err error), )
Simulate executes an action concurrently across N workers using a synchronized barrier to verify thread-safety, race conditions, and singleflight deduplication.
func WaitForJSON ¶ added in v0.3.0
func WaitForJSON[T any]( t testing.TB, timeout, interval time.Duration, request func() (*Response, error), ready func(T) bool, ) T
WaitForJSON repeats request until it returns a decodable JSON value accepted by ready, or timeout expires. request must build a fresh request on every call. Transient request and JSON-decoding errors are retried; the last error is included in the timeout failure when one is available.
This is intended for eventually consistent HTTP APIs. For ordinary one-shot assertions, use suite.GET(...).Do().Into(...) instead.
func WithChaos ¶
WithChaos wraps provided actions with chaos fault injection.
This is the only function in package testkit that references testkit/chaos. Keeping it in its own file makes the dependency boundary explicit: any reader of the package can see at a glance that chaos is opt-in, not part of the core Suite.
Types ¶
type Call ¶ added in v0.4.0
type Call struct {
Seq uint64
Action string
Request any
Response any
Err error
Duration time.Duration
When time.Time
}
Call is one recorded action invocation.
type FakeClock ¶ added in v0.4.0
type FakeClock struct {
// contains filtered or unexported fields
}
FakeClock is a deterministic replacement for time.Now / time.After / time.NewTicker. Install it into a context with WithClock, and code that reads from ctx can be driven forward in tests with Advance.
This is what lets a test verify "the retry waited 200ms" without a real 200ms sleep.
func NewFakeClock ¶ added in v0.4.0
NewFakeClock returns a clock frozen at the given instant.
func (*FakeClock) Advance ¶ added in v0.4.0
Advance moves the clock forward, waking every waiter whose deadline has passed. Waiters are woken in deadline order.
type LoadConfig ¶
type LoadResult ¶
type Recorder ¶
type Recorder[Req, Res any] struct { Retries []RetryEvent[Req] CacheHits int CacheMisses int Coalesced int Deduplicated int // contains filtered or unexported fields }
Recorder implements action.HookDispatcher with full goroutine concurrency safety.
func (*Recorder[Req, Res]) OnCacheHit ¶
func (*Recorder[Req, Res]) OnCacheMiss ¶
func (*Recorder[Req, Res]) OnCoalesced ¶
func (*Recorder[Req, Res]) OnDeduplicated ¶
type Request ¶
type Request struct {
// contains filtered or unexported fields
}
func (*Request) DoContext ¶
DoContext executes the HTTP request with the provided context and returns an error without calling t.Fatalf (goroutine safe).
func (*Request) WithBearerToken ¶
func (*Request) WithCookie ¶
func (*Request) WithHeader ¶
func (*Request) WithMultipartFile ¶
func (*Request) WithTenant ¶
type Response ¶
type Response struct {
// contains filtered or unexported fields
}
func (*Response) BodyString ¶
func (*Response) ContainsString ¶
func (*Response) ExpectBadRequest ¶
func (*Response) ExpectCreated ¶
func (*Response) ExpectErrorKind ¶
func (*Response) ExpectForbidden ¶
func (*Response) ExpectHeader ¶
func (*Response) ExpectNotFound ¶
func (*Response) ExpectStatus ¶
func (*Response) ExpectSuccess ¶
func (*Response) ExpectUnauthorized ¶
type RetryEvent ¶
type StreamCapture ¶
type StreamCapture struct {
// contains filtered or unexported fields
}
func (*StreamCapture) Close ¶
func (sc *StreamCapture) Close()
func (*StreamCapture) Endpoint ¶ added in v0.2.0
Endpoint conveniently extracts an MCP SSE endpoint event.
func (*StreamCapture) WaitForData ¶ added in v0.2.0
func (*StreamCapture) WaitForSSE ¶ added in v0.3.0
func (sc *StreamCapture) WaitForSSE(t testing.TB, timeout time.Duration, match func(SSEEvent) bool) SSEEvent
WaitForSSE waits for the first buffered SSE event accepted by match. Events that do not match are discarded, just like StreamCapture.WaitFor and WaitForData. A nil match predicate is rejected to avoid an accidental match-all wait; use WaitFor with an empty event name when matching any event.
func (*StreamCapture) WaitForSSEData ¶ added in v0.3.0
func (sc *StreamCapture) WaitForSSEData(t testing.TB, substr string, timeout time.Duration) SSEEvent
WaitForSSEData waits for an SSE event whose data contains substr. It is the predicate equivalent of StreamCapture.WaitForData and is useful when a test needs the event name as well as a data substring.
type Suite ¶
type Suite struct {
T testing.TB
Server *httptest.Server
// contains filtered or unexported fields
}
Suite wraps an in-memory or live HTTP server with fluent test assertions.
func NewWithHandler ¶
NewWithHandler initializes a test suite against ANY standard Go http.Handler.
func (*Suite) ListenSSE ¶
func (s *Suite) ListenSSE(path string) *StreamCapture
ListenSSE connects to an SSE endpoint using GET.
func (*Suite) ListenSSEWithRequest ¶ added in v0.2.0
ListenSSEWithRequest connects to an SSE endpoint using a custom HTTP request. This supports POST + SSE patterns such as MCP Streamable HTTP and A2A.
func (*Suite) LoadTest ¶
func (s *Suite) LoadTest(t *testing.T, cfg LoadConfig) LoadResult
LoadTest executes in-process stress tests, properly propagating cancellation to in-flight requests.
func (*Suite) ResetHeaders ¶
func (*Suite) StartBackgroundLoad ¶
func (s *Suite) StartBackgroundLoad(cfg LoadConfig) func()
StartBackgroundLoad generates background load, correctly checking for URL/request errors.
func (*Suite) WithCookie ¶
func (*Suite) WithGlobalBearerToken ¶
func (*Suite) WithGlobalHeader ¶
type Trace ¶ added in v0.4.0
type Trace struct {
// contains filtered or unexported fields
}
Trace records every action invocation that passes through the hook it installs. It is the domainless primitive behind every "did this run, in what order, with what input, how many times" assertion.
Trace is safe for concurrent use. Order is the order in which hook callbacks observed the invocation, which matches the actual scheduling order for sequential code and is a best-effort causal order under concurrency (each entry captures a monotonic sequence number).
func (*Trace) AssertAllSucceeded ¶ added in v0.4.0
AssertAllSucceeded fails the test if any recorded call returned an error.
func (*Trace) AssertCalled ¶ added in v0.4.0
AssertCalled fails the test unless action appears at least once.
func (*Trace) AssertErrorsAt ¶ added in v0.4.0
AssertErrorsAt fails the test unless the named actions failed. Useful for asserting "the *only* failure was X".
func (*Trace) AssertNotCalled ¶ added in v0.4.0
AssertNotCalled fails the test if action appears at least once.
func (*Trace) AssertOrder ¶ added in v0.4.0
AssertOrder fails the test unless first appears before second.
func (*Trace) AssertSequence ¶ added in v0.4.0
AssertSequence fails the test unless the recorded action names equal want exactly (length and order).
func (*Trace) AssertSubsequence ¶ added in v0.4.0
AssertSubsequence fails the test unless want appears as a contiguous run inside the trace.
func (*Trace) Dump ¶ added in v0.4.0
Dump renders the trace as a printable table. Call it inside t.Logf on failure, or use tb.Cleanup(func(){ if tb.Failed() { t.Dump(tb) } }).
func (*Trace) Hook ¶ added in v0.4.0
Hook returns an AnyHook that appends to the trace. Install it on a registry or on individual actions with AddAnyHook or .AnyHook().