HTTP Tests

The testing package in Nimbus provides a fluent TestClient that sends requests directly to your application without spinning up a network listener. Tests run entirely in-process and fast.

Creating a TestClient: testing.New(app)

Pass your *nimbus.App directly to testing.New(app). It automatically ensures the application is warmed up in ModeTest.

import ntest "github.com/CodeSyncr/nimbus/testing"

app := setupTestApp()
client := ntest.New(app)

Fluent Assertions

Chain requests, authentication, and assertions fluently:

func TestUserAPI(t *testing.T) {
    app := setupTestApp()
    client := ntest.New(app)

    // Fluent GET with assertions
    client.Get("/api/users").
        WithBearerToken("secret-token").
        AssertOK(t).
        AssertHeader(t, "Content-Type", "application/json").
        AssertJSONPath(t, "status", "success")

    // Fluent POST JSON
    client.PostJSON("/api/users", map[string]string{"name": "Alice"}).
        AssertCreated(t).
        AssertJSONPath(t, "data.name", "Alice")
}

Direct HTTP Handler Testing: app.ServeHTTP

Because *nimbus.App directly implements standard net/http.Handler, you can test it directly with httptest.ResponseRecorder or pass it to httptest.NewServer(app):

func TestHealthDirect(t *testing.T) {
    app := setupTestApp()
    req := httptest.NewRequest("GET", "/health", nil)
    rec := httptest.NewRecorder()

    app.ServeHTTP(rec, req)

    if rec.Code != http.StatusOK {
        t.Fatalf("expected 200, got %d", rec.Code)
    }
}

GET requests

func TestListUsers(t *testing.T) {
    client := nimbustest.NewTestClient(setupTestApp().Router)

    resp := client.Get("/users")

    if resp.Code != 200 {
        t.Fatalf("expected 200, got %d", resp.Code)
    }
}

POST requests

Post(path, body) sends a POST with Content-Type: application/json. Pass nil for an empty body.

func TestCreatePost(t *testing.T) {
    client := nimbustest.NewTestClient(setupTestApp().Router)

    body := []byte(`{"title":"Hello","content":"World"}`)
    resp := client.Post("/posts", body)

    if resp.Code != 201 {
        t.Fatalf("expected 201, got %d", resp.Code)
    }
}

PUT and DELETE with Do

Use client.Do(req) for PUT, PATCH, DELETE, or any method with custom headers.

func TestUpdateUser(t *testing.T) {
    client := nimbustest.NewTestClient(setupTestApp().Router)

    body := bytes.NewReader([]byte(`{"name":"Bob"}`))
    req := httptest.NewRequest(http.MethodPut, "/users/1", body)
    req.Header.Set("Content-Type", "application/json")
    resp := client.Do(req)

    if resp.Code != 200 {
        t.Fatalf("expected 200, got %d", resp.Code)
    }
}

func TestDeleteUser(t *testing.T) {
    client := nimbustest.NewTestClient(setupTestApp().Router)

    req := httptest.NewRequest(http.MethodDelete, "/users/1", nil)
    resp := client.Do(req)

    if resp.Code != 204 {
        t.Fatalf("expected 204, got %d", resp.Code)
    }
}

Asserting JSON response bodies

Read the response body from the recorder and unmarshal it.

func TestHealthJSON(t *testing.T) {
    client := nimbustest.NewTestClient(setupTestApp().Router)
    resp := client.Get("/health")

    var result map[string]string
    json.Unmarshal(resp.Body.Bytes(), &result)

    if result["status"] != "ok" {
        t.Errorf("expected status ok, got %s", result["status"])
    }
}

Asserting headers

func TestContentType(t *testing.T) {
    client := nimbustest.NewTestClient(setupTestApp().Router)
    resp := client.Get("/health")

    ct := resp.Header().Get("Content-Type")
    if ct != "application/json" {
        t.Errorf("expected application/json, got %s", ct)
    }
}

Testing authenticated routes

Set authorization headers on the request before sending it through Do.

func TestProtectedRoute(t *testing.T) {
    client := nimbustest.NewTestClient(setupTestApp().Router)

    req := httptest.NewRequest(http.MethodGet, "/admin/dashboard", nil)
    req.Header.Set("Authorization", "Bearer test-token-123")
    resp := client.Do(req)

    if resp.Code != 200 {
        t.Fatalf("expected 200, got %d", resp.Code)
    }
}

Testing middleware

Register middleware on a test router and verify its effect (e.g. CORS headers, rate limiting).

func TestCORSMiddleware(t *testing.T) {
    app := nimbus.New()
    app.Router.Use(middleware.CORS("https://example.com"))
    app.Router.Get("/ping", func(c *http.Context) error {
        return c.String(200, "pong")
    })
    client := nimbustest.NewTestClient(app.Router)

    resp := client.Get("/ping")
    origin := resp.Header().Get("Access-Control-Allow-Origin")
    if origin != "https://example.com" {
        t.Errorf("expected CORS origin, got %s", origin)
    }
}

Complete example test file

package main

import (
    "encoding/json"
    "testing"

    "github.com/CodeSyncr/nimbus"
    "github.com/CodeSyncr/nimbus/middleware"
    nimbustest "github.com/CodeSyncr/nimbus/testing"
)

func setupTestApp() *nimbus.App {
    app := nimbus.New()
    app.Router.Use(middleware.Recover())
    app.Router.Get("/health", healthHandler)
    app.Router.Get("/users", listUsersHandler)
    app.Router.Post("/users", createUserHandler)
    return app
}

func TestHealth(t *testing.T) {
    client := nimbustest.NewTestClient(setupTestApp().Router)
    resp := client.Get("/health")
    if resp.Code != 200 {
        t.Fatalf("expected 200, got %d", resp.Code)
    }
    var body map[string]string
    json.Unmarshal(resp.Body.Bytes(), &body)
    if body["status"] != "ok" {
        t.Errorf("expected ok, got %s", body["status"])
    }
}

func TestCreateUser(t *testing.T) {
    client := nimbustest.NewTestClient(setupTestApp().Router)
    resp := client.Post("/users", []byte(`{"name":"Alice"}`))
    if resp.Code != 201 {
        t.Fatalf("expected 201, got %d", resp.Code)
    }
}