Testing
λ Cosmos includes testing utilities in both the router and framework modules. Since everything builds on Go’s standard net/http and net/http/httptest packages, you can use familiar patterns.
Testing Handlers
Section titled “Testing Handlers”Recording Responses
Section titled “Recording Responses”The Handler.Record method executes a handler with a test request and returns the response:
import ( "net/http" "net/http/httptest" "testing"
"github.com/studiolambda/cosmos/framework" "github.com/studiolambda/cosmos/contract/response")
func myHandler(w http.ResponseWriter, r *http.Request) error { return response.JSON(w, http.StatusOK, map[string]string{"status": "ok"})}
func TestMyHandler(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) res := framework.Handler(myHandler).Record(req)
if res.StatusCode != http.StatusOK { t.Errorf("expected 200, got %d", res.StatusCode) }}Record runs the full handler lifecycle including hooks, error handling, and the 204 No Content fallback. Use httptest.NewRequestWithContext(t.Context(), ...) to pass a test context for proper cleanup.
Testing Error Responses
Section titled “Testing Error Responses”func notFoundHandler(w http.ResponseWriter, r *http.Request) error { return problem.Problem{ Title: "Not Found", Status: http.StatusNotFound, }}
func TestNotFound(t *testing.T) { req := httptest.NewRequest("GET", "/missing", nil) req.Header.Set("Accept", "application/json") res := framework.Handler(notFoundHandler).Record(req)
if res.StatusCode != http.StatusNotFound { t.Errorf("expected 404, got %d", res.StatusCode) }}Testing with Middleware
Section titled “Testing with Middleware”Wrap your handler in middleware before recording:
func TestWithMiddleware(t *testing.T) { handler := middleware.Recover()(myHandler)
req := httptest.NewRequest("GET", "/", nil) res := framework.Handler(handler).Record(req) // ...}Testing Routes
Section titled “Testing Routes”The router module provides several testing helpers.
Check if a route pattern is registered:
func TestRouteRegistration(t *testing.T) { app := framework.New() app.Get("/users", listUsers) app.Get("/users/{id}", getUser)
if !app.Has("GET /users") { t.Error("expected /users route to be registered") }
if !app.Has("GET /users/{id}") { t.Error("expected /users/{id} route to be registered") }}Matches
Section titled “Matches”Check if a URL path matches a registered route:
func TestRouteMatching(t *testing.T) { app := framework.New() app.Get("/users/{id}", getUser)
if !app.Matches("GET", "/users/42") { t.Error("expected /users/42 to match") }
if app.Matches("POST", "/users/42") { t.Error("expected POST /users/42 not to match") }}Handler
Section titled “Handler”Retrieve the handler for a registered pattern:
handler, ok := app.Handler("GET /users/{id}")if !ok { t.Fatal("expected handler to be registered")}HandlerMatch
Section titled “HandlerMatch”Retrieve the handler that matches a given method and URL:
handler, pattern, ok := app.HandlerMatch("GET", "/users/42")if !ok { t.Fatal("expected a handler to match")}// pattern == "GET /users/{id}"Record
Section titled “Record”Execute a test request against the router (not just a single handler):
func TestFullStack(t *testing.T) { app := framework.New() app.Use(middleware.Recover()) app.Get("/hello", func(w http.ResponseWriter, r *http.Request) error { return response.String(w, http.StatusOK, "Hello, World!") })
req := httptest.NewRequest("GET", "/hello", nil) res := app.Record(req)
if res.StatusCode != http.StatusOK { t.Errorf("expected 200, got %d", res.StatusCode) }}Router.Record runs the full request through the router’s ServeHTTP, including all middleware, route matching, and error handling.
Integration Tests
Section titled “Integration Tests”For full integration tests that include an actual HTTP server:
func TestIntegration(t *testing.T) { app := framework.New() app.Get("/health", func(w http.ResponseWriter, r *http.Request) error { return response.JSON(w, http.StatusOK, map[string]string{"status": "healthy"}) })
server := httptest.NewServer(app) defer server.Close()
resp, err := http.Get(server.URL + "/health") if err != nil { t.Fatal(err) } defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { t.Errorf("expected 200, got %d", resp.StatusCode) }}Mocking Contracts
Section titled “Mocking Contracts”The contract/mock package provides generated mocks for all contract interfaces (Cache, Database, Session, etc.). These are useful for unit testing handlers that depend on external services:
// Use the mock implementations from contract/mock// to test handlers in isolation without external dependencies.Since all service dependencies are accessed through interfaces (contract.Cache, contract.Database, etc.), you can easily substitute mocks or test doubles using the Provide middleware or by constructing handlers directly with test implementations.