testkit

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

README

nexssp/testkit

Go Reference CI License

Stop testing against mocks. Test against your real HTTP stack.

testkit is the Go testing sidekick that replaces brittle mock layers with real, in-memory HTTP integration tests — in milliseconds.

One harness works with:

Target What you get
nexssp/kernel actions Auto-mount actions, context bridge for tenant/user, route discovery
Standard http.Handler Works with Chi, Gin, Echo, stdlib http.ServeMux — zero kernel required
Live E2E URLs Black-box staging/prod tests using the exact same fluent DSL
JSON-RPC / Stdio transports Test line-delimited protocols (MCP stdio, custom JSON-RPC) with a real client
Why testkit outperforms ordinary testing setups
  • Faster than mock-heavy tests — no reflection on hot paths, no generated mocks, no network hops
  • 🔥 Real HTTP server in memory — exercise your real routing, middleware, and transport
  • 🚫 Zero third-party mock frameworks — no testify/mock, no mock files, no brittle MockRepository
  • 🔍 Automatic route smoke tests — discover and probe every endpoint in milliseconds
  • 📊 In-process load testing — P50/P95/P99 without k6 or wrk
  • 📡 SSE capture — test realtime event streams natively
  • 💣 Chaos injection — latency, 503s, and panics with a few lines
  • 🧩 Deterministic retry scripting — unit-test circuit breakers without fakes
  • 🔌 JSON-RPC / Stdio testing — test non-HTTP transports with the same real-client style
The developer experience
suite := testkit.New(t, GetOrder)

suite.GET("/v1/orders/ord-123").
    Do().
    ExpectOK().
    HasField("customer.tier", "VIP").
    HasField("items.0.sku", "PROD-99").
    ExpectArrayLen("items", 3)

Same DSL, three modes:

// 1. nexssp/kernel actions
suite := testkit.New(t, CreateOrder, GetOrder)

// 2. Any standard http.Handler
suite := testkit.NewWithHandler(t, mux)

// 3. Live E2E
suite := testkit.NewE2E(t, "https://staging-api.example.com")

Key Highlights

  • Dual-Mode Operation: Works natively with nexssp/kernel actions or as a standalone integration toolkit for any standard http.Handler (Chi, Gin, Echo, stdlib http.ServeMux) or live E2E staging URLs.
  • Zero Third-Party Mock Frameworks: No testify/mock, no generated mock files, no reflection on hot paths.
  • Deterministic Sequential Scripting: Script sequential results (e.g. Failure $\to$ Failure $\to$ Success) for testing retry, backoff, and circuit breaker policies.
  • Automated Smoke Testing: Discover and probe all registered routes in parallel to detect panics, invalid path regexes, and unhandled 5xx internal errors in milliseconds.
  • Built-in Resilience & Stress Testing: Run concurrency barriers (thundering-herd simulations), stochastic chaos injection (latency, 503s, panics), and benchmark execution speed with zero allocations.

Installation

go get github.com/nexssp/testkit@latest

Table of Contents

  1. Quick Start: Testing nexssp Actions
  2. Quick Start: Standalone with Any http.Handler
  3. Black-Box E2E Testing
  4. Fluent HTTP Request & Assertion API
  5. Automated Route Smoke Testing
  6. Architectural Contract Verification
  7. Testing Server-Sent Events (SSE)
  8. Deterministic Scripting & Hook Event Recording
  9. Concurrency & Thundering-Herd Barriers
  10. In-Process Load Testing & P99 Latency Profiling
  11. Chaos & Fault Injection
  12. Testing JSON-RPC / Stdio Transports

1. Quick Start: Testing nexssp Actions

When testing nexssp/kernel actions, testkit.New(t, actions...) automatically boots an in-memory loopback HTTP server (httptest.Server), mounts the actions, attaches a context bridge for tenant/user propagation, and manages teardown via t.Cleanup.

package main_test

import (
    "context"
    "testing"

    "github.com/nexssp/kernel/action"
    "github.com/nexssp/testkit"
    "github.com/nexssp/transport/thttp"
)

type UserDTO struct {
    ID    string `json:"id" path:"id"`
    Email string `json:"email"`
}

var GetUser = action.New("user.get", func(ctx context.Context, req UserDTO) (UserDTO, error) {
    return UserDTO{ID: req.ID, Email: "admin@nexss.com"}, nil
}).Route(thttp.GET("/v1/users/{id}")).Build()

func TestUserEndpoint(t *testing.T) {
    suite := testkit.New(t, GetUser)

    suite.GET("/v1/users/usr-42").
        Do().
        ExpectOK().
        HasField("id", "usr-42").
        HasField("email", "admin@nexss.com")
}

2. Quick Start: Standalone with Any http.Handler

You can use testkit without nexssp/kernel. Pass any router or handler implementing standard net/http.Handler to testkit.NewWithHandler(t, handler).

With Standard http.ServeMux (Go 1.22+)
func TestStdlibMux(t *testing.T) {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)
        _, _ = w.Write([]byte(`{"status":"healthy","uptime_sec":3600}`))
    })

    suite := testkit.NewWithHandler(t, mux)

    suite.GET("/healthz").
        Do().
        ExpectOK().
        HasField("status", "healthy").
        HasField("uptime_sec", 3600)
}
With Chi, Gin, or Echo
func TestChiRouter(t *testing.T) {
    r := chi.NewRouter()
    r.Post("/api/v1/orders", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusCreated)
        _, _ = w.Write([]byte(`{"order_id":"ord-900","items":3}`))
    })

    suite := testkit.NewWithHandler(t, r)

    suite.POST("/api/v1/orders", map[string]any{"sku": "A1"}).
        Do().
        ExpectCreated().
        HasField("order_id", "ord-900").
        HasField("items", 3)
}

3. Black-Box E2E Testing

Use testkit.NewE2E(t, baseURL) to run the same assertions against a remote container, staging server, or live deployment.

func TestStagingDeployment_E2E(t *testing.T) {
    suite := testkit.NewE2E(t, "https://staging-api.example.com")

    suite.GET("/v1/system/status").
        WithHeader("X-Api-Key", "secret-key").
        Do().
        ExpectOK().
        HasField("environment", "staging")
}

4. Fluent HTTP Request & Assertion API

Request Builder Capabilities
suite.POST("/v1/invoices").
    WithQuery("draft", "true").
    WithQueries(map[string]string{"lang": "pl", "currency": "PLN"}).
    WithHeader("X-Custom-Trace", "trace-123").
    WithBearerToken("jwt-token-here").
    WithTenant("tenant-corp-pl").               // Injects X-Tenant-ID
    WithCookie("session_id", "sess-abc").
    WithJSON(map[string]any{
        "customer": "Acme Corp",
        "amount":   1250.50,
    }).
    Do()
Multipart File Uploads
suite.POST("/v1/documents/upload").
    WithMultipartFile("file", "invoice.pdf", []byte("%PDF-1.4...")).
    Do().
    ExpectCreated()
Response Assertions & JSON Path Traversal

testkit uses dot-separated JSON path traversal for inspecting nested objects and arrays without unmarshaling into ad-hoc structs.

suite.GET("/v1/orders/ord-123").
    Do().
    ExpectOK().
    ExpectHeader("Content-Type", "application/json").
    HasField("data.order.id", "ord-123").
    HasField("data.order.customer.tier", "VIP").
    HasField("data.order.items.0.sku", "PROD-99"). // Indexed array lookup
    ExpectArrayLen("data.order.items", 3).
    ContainsString("Acme Corp")
Typed Deserialization & Structured Error Validation
var order OrderResponse

suite.GET("/v1/orders/ord-123").
    Do().
    ExpectOK().
    Into(&order) // Unmarshals JSON response body into target struct

// Verify categorized AppError output (xerr.Kind)
suite.GET("/v1/orders/missing-id").
    Do().
    ExpectNotFound().
    ExpectErrorKind(xerr.KindNotFound, "order not found")

5. Automated Route Smoke Testing

RunSmokeTests inspects all mounted actions, synthesizes valid minimal payloads using struct reflection, substitutes path parameters (e.g. {id} $\to$ test-id), and executes baseline HTTP calls.

func TestAllEndpoints_Smoke(t *testing.T) {
    allActions := []action.AnyAction{
        CreateOrder,
        GetOrder,
        ListOrders,
        SettleInvoice,
        DeleteInvoice,
    }

    // Probes every route with synthetic inputs to ensure no endpoint crashes
    testkit.RunSmokeTests(t, allActions)
}

Passing Criterion: Any status code < 500 (including 200 OK, 201 Created, 400 Bad Request, or 401 Unauthorized) passes. Status 5xx (panic, nil pointer dereference, unhandled runtime error) fails the test.


6. Architectural Contract Verification

AssertContracts enforces system-wide design invariants across your codebase:

  • No Duplicate Action Names: Prevents conflicting route registrations.
  • No Orphaned Actions: Catches actions created without route bindings or hooks.
  • Valid Payload Structs: Ensures requests are structured as struct or map types (preventing naked primitives on API boundaries).
func TestSystemInvariants_Contracts(t *testing.T) {
    allActions := []action.AnyAction{
        CreateOrder,
        GetOrder,
        SettleInvoice,
    }

    testkit.AssertContracts(t, allActions)
}

7. Testing Server-Sent Events (SSE)

testkit provides a dedicated SSE capture engine that connects to a stream, confirms the initial handshake, and buffers incoming stream chunks:

func TestRealtimeTelemetry_SSE(t *testing.T) {
    suite := testkit.New(t, TelemetryStreamAction)

    // Connects and waits for the initial HTTP handshake
    stream := suite.ListenSSE("/events/telemetry")
    defer stream.Close()

    // Trigger an event on the server
    suite.POST("/v1/trigger-event", map[string]string{"msg": "ping"}).Do().ExpectOK()

    // Block until a matching event arrives or timeout expires
    event := stream.WaitFor(t, "message", 2*time.Second)

    if !strings.Contains(event.Data, "ping") {
        t.Fatalf("unexpected SSE payload: %s", event.Data)
    }
}

For MCP-style SSE handshakes, Endpoint() extracts the event: endpoint data, and WaitForData() blocks until an event contains a substring:

stream := suite.ListenSSE("/mcp/sse")
endpoint := stream.Endpoint(t, 2*time.Second) // "/mcp/message?sessionId=..."

// POST to endpoint...

msg := stream.WaitForData(t, "result", 2*time.Second)

For A2A / MCP Streamable HTTP (POST + SSE), use ListenSSEWithRequest:

req := httptest.NewRequest(http.MethodPost, "/endpoint",
    strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"message/send"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "text/event-stream")

stream := suite.ListenSSEWithRequest(t, req)
defer stream.Close()

evt := stream.WaitFor(t, "message", 2*time.Second)

8. Deterministic Scripting & Hook Event Recording

For unit-testing retry policies, circuit breakers, and deduplication layers without third-party mocks:

Deterministic Action Scripting

testkit.Script generates an execution function that returns supplied results in sequence and repeats the final result.

func TestRetryPolicy_RecoversOnThirdAttempt(t *testing.T) {
    // 1st attempt: 503 Unavailable
    // 2nd attempt: 503 Unavailable
    // 3rd attempt: 200 Success
    scriptedFn := testkit.Script[int, string](
        testkit.Failure[string](xerr.Unavailable("database connection timeout")),
        testkit.Failure[string](xerr.Unavailable("database connection timeout")),
        testkit.Success("order_settled"),
    )

    act := action.New("order.settle", scriptedFn).
        Retry(3, action.ConstantBackoff(1*time.Millisecond)).
        Build()

    res, err := act.Do(context.Background(), 42)
    if err != nil || res != "order_settled" {
        t.Fatalf("expected recovery on attempt 3, got res=%q, err=%v", res, err)
    }
}
Hook Event Recording

testkit.Recorder tracks internal middleware events (cache hits, misses, retries, deduplication, and coalescing).

func TestRecorder_CapturesRetryTelemetry(t *testing.T) {
    rec := new(testkit.Recorder[int, string])

    scriptedFn := testkit.Script[int, string](
        testkit.Failure[string](xerr.Unavailable("transient failure")),
        testkit.Success("recovered"),
    )

    act := action.New("recorder.test", scriptedFn).
        Retry(2, action.ConstantBackoff(0)).
        Build()

    act.AddAnyHook(action.AnyHook{
        OnRetry: func(ctx context.Context, req any, attempt int, err error, meta *action.Meta) {
            rec.OnRetry(ctx, req.(int), attempt, err)
        },
    })

    res, err := act.Do(context.Background(), 100)
    if err != nil || res != "recovered" {
        t.Fatalf("unexpected execution result: %v", err)
    }

    if len(rec.Retries) != 1 || rec.Retries[0].Attempt != 1 {
        t.Fatalf("expected 1 recorded retry event, got %+v", rec.Retries)
    }
}

9. Concurrency & Thundering-Herd Barriers

testkit.Simulate synchronizes $N$ worker goroutines behind an atomic start barrier to verify thread safety and request deduplication.

func TestSingleflight_ThunderingHerdShield(t *testing.T) {
    var dbCalls atomic.Int32

    fetchPrice := action.New("price.fetch", func(ctx context.Context, sku string) (float64, error) {
        dbCalls.Add(1)
        time.Sleep(20 * time.Millisecond) // Simulated slow DB lookup
        return 199.99, nil
    }).
        Dedup(func(sku string) string { return sku }).
        Build()

    // Dispatch 50 concurrent requests simultaneously at the exact same millisecond
    testkit.Simulate(t, fetchPrice, "SKU-PROD-1", 50, func(t testing.TB, price float64, err error) {
        if err != nil || price != 199.99 {
            t.Errorf("unexpected worker result: %v, err=%v", price, err)
        }
    })

    // Verify only 1 database query executed
    if got := dbCalls.Load(); got != 1 {
        t.Fatalf("thundering herd shield failed: expected 1 DB call, got %d", got)
    }
}

10. In-Process Load Testing & P99 Latency Profiling

In-Process Stress Testing

Execute load tests directly within go test and calculate throughput and percentile latencies without spawning external tools like k6 or wrk.

func TestEndpoint_LoadPerformance(t *testing.T) {
    suite := testkit.New(t, GetOrder)

    results := suite.LoadTest(t, testkit.LoadConfig{
        Concurrency: 16,
        Duration:    3 * time.Second,
        Method:      "GET",
        Path:        "/v1/orders/ord-1",
    })

    t.Logf("Total Reqs: %d | RPS: %.2f | Error Rate: %.2f%%",
        results.TotalRequests, results.RPS, results.ErrorRate*100)
    t.Logf("Latencies: P50=%v | P95=%v | P99=%v",
        results.P50, results.P95, results.P99)

    if results.ErrorRate > 0.01 {
        t.Fatalf("error rate exceeded 1%%: %.4f", results.ErrorRate)
    }
    if results.P95 > 50*time.Millisecond {
        t.Fatalf("P95 latency exceeded 50ms: %v", results.P95)
    }
}
Background Load Generator for pprof Profiling
func TestProfileUnderSustainedLoad(t *testing.T) {
    suite := testkit.New(t, GetOrder)

    // Runs simulated background traffic in a goroutine until stop() is called
    stop := suite.StartBackgroundLoad(testkit.LoadConfig{
        Concurrency: 8,
        Method:      "GET",
        Path:        "/v1/orders/ord-1",
    })
    defer stop()

    // Perform specific diagnostic assertions while the system is under sustained load
    time.Sleep(2 * time.Second)
}

11. Chaos & Fault Injection

testkit/chaos injects controlled failure modes directly into actions or suites to verify that your circuit breakers, retries, and error boundaries work under stress.

import "github.com/nexssp/testkit/chaos"

func TestResilience_UnderNetworkChaos(t *testing.T) {
    // Wrap actions with stochastic fault injection
    actions := testkit.WithChaos([]action.AnyAction{CreateOrder}, chaos.Config{
        Enabled:   true,
        ErrorRate: 0.25,                  // 25% simulated transient 503 errors
        MaxDelay:  150 * time.Millisecond, // Up to 150ms random network latency
        PanicRate: 0.02,                  // 2% unexpected panics to test recovery
    })

    suite := testkit.New(t, actions)

    // Run traffic to verify RetryMiddleware and PanicRecovery protect clients
    suite.POST("/v1/orders", map[string]any{"sku": "A"}).
        Do().
        ExpectSuccess()
}

12. Testing JSON-RPC / Stdio Transports

Use testkit/rpc for line-delimited JSON-RPC transports such as MCP stdio. It dials an in-memory net.Pipe and speaks JSON-RPC 2.0 exactly like a real client.

import (
    "context"
    "io"
    "testing"

    "github.com/nexssp/testkit/rpc"
)

func TestMCP_Stdio(t *testing.T) {
    client := rpc.DialJSONRPC(t, func(ctx context.Context, in io.Reader, out io.Writer) error {
        return mcpServer.Serve(ctx, in, out)
    })

    resp := client.Call("tools/list", nil, 1)

    var data struct {
        Tools []struct {
            Name string `json:"name"`
        } `json:"tools"`
    }
    resp.BindResult(t, &data)

    // Assert with standard Go testing
    if len(data.Tools) != 1 {
        t.Fatalf("expected 1 tool, got %d", len(data.Tools))
    }
}
rpc.Client API
client := rpc.DialJSONRPC(t, serveFunc)

// Request / response
resp := client.Call("tools/list", nil, 1)

// Notification (no response)
_ = client.Notify("notifications/initialized", nil)

// Raw payload line (e.g. parse-error tests)
resp = client.CallRaw(`{"jsonrpc":"2.0","id":1,"method":"ping"}`)

// Typed result binding
resp.BindResult(t, &myStruct)

// JSON-RPC error object
if resp.Error != nil {
    t.Fatalf("RPC error: %+v", resp.Error)
}

License

Apache License 2.0. See LICENSE for details.

Documentation

Overview

Package testkit provides deterministic, fluent test harnesses for both nexssp actions and standard Go http.Handler implementations.

Index

Constants

View Source
const (
	HeaderTenantID = "X-Tenant-ID"
)

Variables

This section is empty.

Functions

func AssertContracts

func AssertContracts(t *testing.T, actions []action.AnyAction)

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 BenchHTTP

func BenchHTTP(b *testing.B, s *Suite, method, path string, payload any)

BenchHTTP benchmarks the complete HTTP stack over in-memory sockets.

func MustError

func MustError[Req, Res any](t testing.TB, fn action.Fn[Req, Res], req Req, want error)

func MustExecute

func MustExecute[Req, Res any](t testing.TB, fn action.Fn[Req, Res], req Req) Res

func RunSmokeTests

func RunSmokeTests(t *testing.T, actions []action.AnyAction)

func Script

func Script[Req, Res any](results ...Result[Res]) action.Fn[Req, Res]

Script returns a thread-safe deterministic action function.

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 WithChaos

func WithChaos(actions []action.AnyAction, cfg chaos.Config) []action.AnyAction

WithChaos wraps provided actions with chaos fault injection.

Types

type LoadConfig

type LoadConfig struct {
	Concurrency int
	Duration    time.Duration
	Method      string
	Path        string
	Payload     any
}

type LoadResult

type LoadResult struct {
	TotalRequests int64
	Errors        int64
	ErrorRate     float64
	RPS           float64
	P50           time.Duration
	P95           time.Duration
	P99           time.Duration
}

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 (r *Recorder[Req, Res]) OnCacheHit(context.Context, Req, Res)

func (*Recorder[Req, Res]) OnCacheMiss

func (r *Recorder[Req, Res]) OnCacheMiss(context.Context, Req)

func (*Recorder[Req, Res]) OnCoalesced

func (r *Recorder[Req, Res]) OnCoalesced(context.Context, Req)

func (*Recorder[Req, Res]) OnDeduplicated

func (r *Recorder[Req, Res]) OnDeduplicated(context.Context, Req)

func (*Recorder[Req, Res]) OnRetry

func (r *Recorder[Req, Res]) OnRetry(_ context.Context, req Req, attempt int, err error)

type Request

type Request struct {
	// contains filtered or unexported fields
}

func (*Request) Do

func (r *Request) Do() *Response

Do executes the request and immediately fails the test if an error occurs.

func (*Request) DoContext

func (r *Request) DoContext(ctx context.Context) (*Response, error)

DoContext executes the HTTP request with the provided context and returns an error without calling t.Fatalf (goroutine safe).

func (*Request) DoE

func (r *Request) DoE() (*Response, error)

DoE executes the HTTP request using the request's configured context.

func (*Request) WithBearerToken

func (r *Request) WithBearerToken(token string) *Request

func (*Request) WithContext

func (r *Request) WithContext(ctx context.Context) *Request

func (*Request) WithCookie

func (r *Request) WithCookie(name, value string) *Request

func (*Request) WithForm

func (r *Request) WithForm(data map[string]string) *Request

func (*Request) WithHeader

func (r *Request) WithHeader(k, v string) *Request

func (*Request) WithJSON

func (r *Request) WithJSON(v any) *Request

func (*Request) WithMultipartFile

func (r *Request) WithMultipartFile(fieldName, filename string, content []byte) *Request

func (*Request) WithQueries

func (r *Request) WithQueries(params map[string]string) *Request

func (*Request) WithQuery

func (r *Request) WithQuery(key, value string) *Request

func (*Request) WithTenant

func (r *Request) WithTenant(tenantID string) *Request

type Response

type Response struct {
	// contains filtered or unexported fields
}

func (*Response) Body

func (c *Response) Body() []byte

func (*Response) BodyString

func (c *Response) BodyString() string

func (*Response) ContainsString

func (c *Response) ContainsString(substr string) *Response

func (*Response) ExpectArrayLen

func (c *Response) ExpectArrayLen(path string, n int) *Response

func (*Response) ExpectBadRequest

func (c *Response) ExpectBadRequest() *Response

func (*Response) ExpectCreated

func (c *Response) ExpectCreated() *Response

func (*Response) ExpectErrorKind

func (c *Response) ExpectErrorKind(kind xerr.Kind, message string) *Response

func (*Response) ExpectForbidden

func (c *Response) ExpectForbidden() *Response

func (*Response) ExpectHeader

func (c *Response) ExpectHeader(key, value string) *Response

func (*Response) ExpectNotFound

func (c *Response) ExpectNotFound() *Response

func (*Response) ExpectOK

func (c *Response) ExpectOK() *Response

func (*Response) ExpectStatus

func (c *Response) ExpectStatus(code int) *Response

func (*Response) ExpectSuccess

func (c *Response) ExpectSuccess() *Response

func (*Response) ExpectUnauthorized

func (c *Response) ExpectUnauthorized() *Response

func (*Response) HasField

func (c *Response) HasField(path string, expected any) *Response

func (*Response) Header

func (c *Response) Header(k string) string

func (*Response) Into

func (c *Response) Into(v any) *Response

func (*Response) Status

func (c *Response) Status() int

type Result

type Result[Res any] struct {
	Value Res
	Err   error
}

func Failure

func Failure[Res any](err error) Result[Res]

func Success

func Success[Res any](value Res) Result[Res]

type RetryEvent

type RetryEvent[Req any] struct {
	Request Req
	Attempt int
	Err     error
}

type SSEEvent

type SSEEvent struct {
	Event string
	Data  string
}

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

func (sc *StreamCapture) Endpoint(t testing.TB, timeout time.Duration) string

Endpoint conveniently extracts an MCP SSE endpoint event.

func (*StreamCapture) WaitFor

func (sc *StreamCapture) WaitFor(t testing.TB, eventName string, timeout time.Duration) SSEEvent

func (*StreamCapture) WaitForData added in v0.2.0

func (sc *StreamCapture) WaitForData(t testing.TB, substr string, timeout time.Duration) SSEEvent

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 New

func New(t testing.TB, providers ...any) *Suite

New flattens nexssp actions and mounts them onto an in-memory transport.

func NewE2E

func NewE2E(t testing.TB, baseURL string) *Suite

NewE2E initializes the suite against a live, external URL.

func NewWithHandler

func NewWithHandler(t testing.TB, h http.Handler) *Suite

NewWithHandler initializes a test suite against ANY standard Go http.Handler.

func (*Suite) DELETE

func (s *Suite) DELETE(path string, body ...any) *Request

func (*Suite) GET

func (s *Suite) GET(path string, body ...any) *Request

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

func (s *Suite) ListenSSEWithRequest(t testing.TB, req *http.Request) *StreamCapture

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) PATCH

func (s *Suite) PATCH(path string, body ...any) *Request

func (*Suite) POST

func (s *Suite) POST(path string, body ...any) *Request

func (*Suite) PUT

func (s *Suite) PUT(path string, body ...any) *Request

func (*Suite) Request

func (s *Suite) Request(method, path string) *Request

func (*Suite) ResetHeaders

func (s *Suite) ResetHeaders() *Suite

func (*Suite) StartBackgroundLoad

func (s *Suite) StartBackgroundLoad(cfg LoadConfig) func()

StartBackgroundLoad generates background load, correctly checking for URL/request errors.

func (*Suite) WithCookie

func (s *Suite) WithCookie(name, value string) *Suite

func (*Suite) WithGlobalBearerToken

func (s *Suite) WithGlobalBearerToken(token string) *Suite

func (*Suite) WithGlobalHeader

func (s *Suite) WithGlobalHeader(key, value string) *Suite

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL