kitoko

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Mar 28, 2026 License: MIT Imports: 16 Imported by: 1

README

Kitoko

Kitoko is a lightweight, fluent HTTP client and testing library for Go.

It is designed to be:

  • A production-ready HTTP client (microservices, external APIs, internal communication)
  • A powerful HTTP testing library with expressive, built-in assertions

Kitoko keeps your HTTP code clean, readable, and chainable — both in production and in tests.


Installation

go get github.com/jkaninda/kitoko

Quick Start

Production HTTP Client

client := kitoko.NewClient("https://api.example.com")
client.Headers["Authorization"] = "Bearer my-token"

resp, err := client.GET("/users").Execute()
if err != nil {
    log.Fatal(err)
}

fmt.Println(resp.StatusCode) // 200
fmt.Println(resp.String())   // response body as string

var users []User
if err := resp.JSON(&users); err != nil {
    log.Fatal(err)
}

Testing HTTP Endpoints

func TestAPI(t *testing.T) {
    tc := kitoko.NewTestClient(t, server.URL)

    tc.GET("/users").
        SetBearerAuth("my-token").
        ExpectStatusOK().
        ExpectHeaderContains("Content-Type", "application/json").
        ExpectJSONPath("name", "Alice")
}

Features

Core
  • Dual-purpose: Production client + Test library
  • Fluent, chainable API
  • All HTTP methods supported: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
  • Per-request timeout (default: 30s)
  • Custom http.Client support
Request Capabilities
  • JSON body (struct, map, string)
  • Form-encoded body
  • Multipart form-data
  • File upload (disk or io.Reader)
  • Query parameters (single & batch)
  • Batch headers
  • Basic & Bearer authentication
  • Cookie support
  • Cookie jar (session persistence)
Testing & Assertions
  • Built-in status assertions
  • Body & header assertions
  • Cookie assertions
  • JSON assertions (full object or path-based)
  • Dot-notation JSON path (user.name)
  • httptest.ResponseRecorder integration
  • Wrap production requests with test assertions

Production Client

Client with Base URL

client := kitoko.NewClient("https://api.example.com")
client.Headers["Authorization"] = "Bearer token"

// GET
resp, err := client.GET("/users").Execute()

// POST with JSON body
resp, err := client.POST("/users").
    JSONBody(map[string]string{"name": "Alice"}).
    Execute()

// PUT with struct
resp, err := client.PUT("/users/1").
    JSONBody(User{Name: "Bob", Age: 30}).
    Execute()

// DELETE
resp, err := client.DELETE("/users/1").Execute()

client := kitoko.NewClientWithCookieJar("https://api.example.com")

// Login — server sets session cookie
_, err := client.POST("/login").
    JSONBody(map[string]string{"user": "admin", "pass": "secret"}).
    Execute()

// Cookie automatically sent
resp, err := client.GET("/profile").Execute()

One-Off Requests

resp, err := kitoko.NewRequest().
    Method("GET").
    URL("https://api.example.com/health").
    Header("X-Request-ID", "abc123").
    Timeout(5 * time.Second).
    Execute()

Do() is available as an alias for Execute():

resp, err := kitoko.NewRequest().
    Method("GET").
    URL("https://api.example.com/health").
    Do()

Request Builder

Full control over request construction:

resp, err := kitoko.NewRequest().
    Method(http.MethodPost).
    URL("https://api.example.com").
    Path("/users").
    Header("X-Request-ID", "abc123").
    QueryParam("page", "1").
    JSONBody(`{"name":"Alice"}`).
    SetBearerAuth("token").
    Timeout(5 * time.Second).
    Execute()

Batch Headers & Query Parameters

resp, err := client.GET("/search").
    Headers(map[string]string{
        "X-Request-ID": "abc123",
        "Accept":       "application/json",
    }).
    QueryParams(map[string]string{
        "q":     "kitoko",
        "page":  "1",
        "limit": "20",
    }).
    Execute()

Form Data

resp, err := client.POST("/login").
    FormBody(map[string]string{
        "username": "admin",
        "password": "secret",
    }).
    Execute()

File Uploads

// Single file from disk
resp, err := client.POST("/upload").
    FileUpload("avatar", "/path/to/photo.jpg").
    Execute()

// Multiple files with form fields
resp, err := client.POST("/upload").
    MultipartBody(
        map[string]string{"description": "My documents"},
        kitoko.FileField{
            FieldName: "doc",
            FileName:  "report.pdf",
            Content:   file, // any io.Reader
        },
    ).
    Execute()

Authentication

// Basic Auth
resp, err := client.GET("/secure").
    SetBasicAuth("admin", "password").
    Execute()

// Bearer Token
resp, err := client.GET("/secure").
    SetBearerAuth("eyJhbGci...").
    Execute()

Cookies

// Single cookie
resp, err := client.GET("/dashboard").
    SetCookie("token", "my-secret").
    Execute()

// Multiple cookies
resp, err := client.GET("/dashboard").
    SetCookies([]*http.Cookie{
        {Name: "session", Value: "abc"},
        {Name: "theme", Value: "dark"},
    }).
    Execute()

Package-Level Requests

resp, err := kitoko.Get("https://api.example.com/data").Do()

resp, err := kitoko.Post("https://api.example.com/users").
    JSONBody(map[string]string{"name": "Alice"}).
    Do()

resp, err := kitoko.Put("https://api.example.com/users/1").
    JSONBody(map[string]string{"name": "Bob"}).
    SetBearerAuth("my-token").
    Execute()

resp, err := kitoko.Delete("https://api.example.com/users/1").Execute()

Custom HTTP Client

resp, err := kitoko.NewRequest().
    Method("GET").
    URL("https://api.example.com/data").
    WithHTTPClient(&http.Client{
        Timeout: 10 * time.Second,
        Transport: &http.Transport{
            MaxIdleConns: 10,
        },
    }).
    Execute()

Testing

TestClient

func TestAPI(t *testing.T) {
    tc := kitoko.NewTestClient(t, server.URL)
    tc.Headers["Authorization"] = "Bearer test-token"

    tc.GET("/api/users").ExpectStatusOK()
    tc.POST("/api/users").JSONBody(user).ExpectStatusCreated()
    tc.DELETE("/api/users/1").ExpectStatusNoContent()
}

func TestLoginFlow(t *testing.T) {
    tc := kitoko.NewTestClientWithCookieJar(t, server.URL)

    tc.POST("/login").
        JSONBody(map[string]string{"user": "admin", "pass": "secret"}).
        ExpectStatusOK()

    tc.GET("/profile").
        ExpectStatusOK().
        ExpectJSONPath("user", "admin")
}

Package-Level Helpers

kitoko.GET(t, server.URL+"/health").ExpectStatusOK()

kitoko.POST(t, server.URL+"/users").
    JSONBody(map[string]string{"name": "Alice"}).
    ExpectStatusCreated().
    ExpectJSONPath("name", "Alice")

Full control:

kitoko.Request(t).
    Method("GET").
    URL(server.URL + "/users").
    Header("Accept", "application/json").
    ExpectStatusOK()

Assertions

Status Codes

rb.ExpectStatus(201)
rb.ExpectStatusOK()
rb.ExpectStatusCreated()
rb.ExpectStatusAccepted()
rb.ExpectStatusNoContent()
rb.ExpectStatusBadRequest()
rb.ExpectStatusUnauthorized()
rb.ExpectStatusForbidden()
rb.ExpectStatusNotFound()
rb.ExpectStatusConflict()
rb.ExpectStatusInternalServerError()

Body

rb.ExpectBody("exact match")
rb.ExpectBodyContains("substring")
rb.ExpectContains("alias")
rb.ExpectBodyNotContains("unexpected")
rb.ExpectEmptyBody()

JSON

// Full structure
rb.ExpectJSON(map[string]any{
    "name": "Alice",
    "age":  float64(30),
})

// Path assertion (dot notation)
rb.ExpectJSONPath("user.name", "Alice")
rb.ExpectJSONPath("config.timeout", float64(30))

// Parse into struct
var user User
rb.ParseJSON(&user)

Headers

rb.ExpectHeader("Content-Type", "application/json")
rb.ExpectHeaderContains("Content-Type", "json")
rb.ExpectHeaderExists("X-Request-ID")
rb.ExpectContentType("application/json")

Cookies

rb.ExpectCookie("session", "abc123")
rb.ExpectCookieExist("session")

Wrap Production Requests for Testing

rb := kitoko.NewRequest().
    Method("GET").
    URL(server.URL + "/users")

kitoko.Expect(t, rb).ExpectStatusOK()

httptest.ResponseRecorder Integration

rec := httptest.NewRecorder()
handler.ServeHTTP(rec, httptest.NewRequest("GET", "/api/users", nil))

kitoko.FromRecorder(t, rec).
    ExpectStatusOK().
    ExpectJSONPath("name", "Alice")

Manual Execution in Tests

resp, body := kitoko.GET(t, server.URL+"/data").Execute()

fmt.Println(resp.StatusCode)
fmt.Println(string(body))

Utilities

GracefulExitAfter

Sends a SIGTERM signal to the current process after a duration — useful for testing graceful shutdown behavior.

kitoko.GracefulExitAfter(30 * time.Second)

Design Philosophy

Kitoko is built around three principles:

  1. Fluent over verbose
  2. Readable tests
  3. Minimal abstraction over net/http

No magic. No hidden behavior. Just expressive HTTP.


License

MIT License — see LICENSE.

Copyright (c) 2026 Jonas Kaninda

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GracefulExitAfter

func GracefulExitAfter(duration time.Duration)

Types

type Client

type Client struct {
	// BaseURL is the base URL prepended to all request paths.
	BaseURL string
	// Headers are default headers included in every request.
	Headers map[string]string
	// HTTPClient is the underlying http.Client used for requests.
	HTTPClient *http.Client
}

Client is a general-purpose HTTP client with a base URL and default headers. Use NewClient to create one. For testing with assertions, use NewTestClient instead.

func NewClient

func NewClient(baseURL string) *Client

NewClient creates a new Client with the specified base URL.

func NewClientWithCookieJar

func NewClientWithCookieJar(baseURL string) *Client

NewClientWithCookieJar creates a new Client with a cookie jar that automatically persists cookies across requests.

func (*Client) DELETE

func (c *Client) DELETE(path string) *RequestBuilder

func (*Client) GET

func (c *Client) GET(path string) *RequestBuilder

func (*Client) HEAD

func (c *Client) HEAD(path string) *RequestBuilder

func (*Client) OPTIONS

func (c *Client) OPTIONS(path string) *RequestBuilder

func (*Client) PATCH

func (c *Client) PATCH(path string) *RequestBuilder

func (*Client) POST

func (c *Client) POST(path string) *RequestBuilder

func (*Client) PUT

func (c *Client) PUT(path string) *RequestBuilder

type FileField

type FileField struct {
	FieldName string
	FileName  string
	Content   io.Reader
}

FileField represents a file to be included in a multipart form upload.

type RequestBuilder

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

RequestBuilder provides a fluent API for building and executing HTTP requests. Use NewRequest() to create one, or use Client methods for a base-URL-scoped builder.

func Delete added in v0.0.2

func Delete(url string) *RequestBuilder

func Get added in v0.0.2

func Get(url string) *RequestBuilder
func Head(url string) *RequestBuilder

func NewRequest

func NewRequest() *RequestBuilder

NewRequest creates a new RequestBuilder for building an HTTP request.

func Options added in v0.0.2

func Options(url string) *RequestBuilder

func Patch added in v0.0.2

func Patch(url string) *RequestBuilder

func Post added in v0.0.2

func Post(url string) *RequestBuilder

func Put added in v0.0.2

func Put(url string) *RequestBuilder

func (*RequestBuilder) Body

func (rb *RequestBuilder) Body(body io.Reader) *RequestBuilder

func (*RequestBuilder) Do

func (rb *RequestBuilder) Do() (*Response, error)

Do is an alias for Execute.

func (*RequestBuilder) Execute

func (rb *RequestBuilder) Execute() (*Response, error)

Execute performs the HTTP request and returns the Response.

func (*RequestBuilder) FileUpload

func (rb *RequestBuilder) FileUpload(fieldName, filePath string) *RequestBuilder

FileUpload adds a file from disk to a multipart form upload.

func (*RequestBuilder) FormBody

func (rb *RequestBuilder) FormBody(values map[string]string) *RequestBuilder

func (*RequestBuilder) Header

func (rb *RequestBuilder) Header(k, v string) *RequestBuilder

func (*RequestBuilder) Headers

func (rb *RequestBuilder) Headers(headers map[string]string) *RequestBuilder

func (*RequestBuilder) JSONBody

func (rb *RequestBuilder) JSONBody(v any) *RequestBuilder

func (*RequestBuilder) Method

func (rb *RequestBuilder) Method(method string) *RequestBuilder

func (*RequestBuilder) MultipartBody

func (rb *RequestBuilder) MultipartBody(fields map[string]string, files ...FileField) *RequestBuilder

MultipartBody sets the request body as a multipart form with text fields and file attachments.

func (*RequestBuilder) Path

func (rb *RequestBuilder) Path(path string) *RequestBuilder

Path appends a path segment to the existing URL.

func (*RequestBuilder) QueryParam

func (rb *RequestBuilder) QueryParam(key, value string) *RequestBuilder

func (*RequestBuilder) QueryParams

func (rb *RequestBuilder) QueryParams(params map[string]string) *RequestBuilder

func (*RequestBuilder) SetBasicAuth

func (rb *RequestBuilder) SetBasicAuth(username, password string) *RequestBuilder

func (*RequestBuilder) SetBearerAuth

func (rb *RequestBuilder) SetBearerAuth(token string) *RequestBuilder

func (*RequestBuilder) SetCookie

func (rb *RequestBuilder) SetCookie(name, value string) *RequestBuilder

SetCookie adds a cookie to the request.

func (*RequestBuilder) SetCookies

func (rb *RequestBuilder) SetCookies(cookies []*http.Cookie) *RequestBuilder

SetCookies adds multiple cookies to the request.

func (*RequestBuilder) Timeout

func (rb *RequestBuilder) Timeout(timeout time.Duration) *RequestBuilder

func (*RequestBuilder) URL

func (rb *RequestBuilder) URL(rawURL string) *RequestBuilder

URL sets the request URL.

func (*RequestBuilder) WithHTTPClient

func (rb *RequestBuilder) WithHTTPClient(client *http.Client) *RequestBuilder

WithHTTPClient sets a custom http.Client for this request.

type Response

type Response struct {
	// StatusCode is the HTTP status code (e.g. 200).
	StatusCode int
	// Status is the HTTP status line (e.g. "200 OK").
	Status string
	// Header contains the response headers.
	Header http.Header
	// Body is the raw response body bytes.
	Body []byte
	// Raw is the underlying *http.Response.
	Raw *http.Response
}

Response wraps an HTTP response with convenience methods.

func (*Response) Cookie

func (r *Response) Cookie(name string) *http.Cookie

Cookie returns the named cookie from the response, or nil if not found.

func (*Response) JSON

func (r *Response) JSON(target any) error

JSON unmarshals the response body into the target.

func (*Response) String

func (r *Response) String() string

String returns the response body as a string.

type TestClient

type TestClient struct {
	*Client
	// contains filtered or unexported fields
}

func NewTestClient

func NewTestClient(t *testing.T, baseURL string) *TestClient

NewTestClient creates a new TestClient for testing with the specified base URL.

func NewTestClientWithCookieJar

func NewTestClientWithCookieJar(t *testing.T, baseURL string) *TestClient

NewTestClientWithCookieJar creates a new TestClient with a cookie jar for session persistence.

func (*TestClient) DELETE

func (tc *TestClient) DELETE(path string) *TestRequestBuilder

func (*TestClient) GET

func (tc *TestClient) GET(path string) *TestRequestBuilder

func (*TestClient) HEAD

func (tc *TestClient) HEAD(path string) *TestRequestBuilder

func (*TestClient) OPTIONS

func (tc *TestClient) OPTIONS(path string) *TestRequestBuilder

func (*TestClient) PATCH

func (tc *TestClient) PATCH(path string) *TestRequestBuilder

func (*TestClient) POST

func (tc *TestClient) POST(path string) *TestRequestBuilder

func (*TestClient) PUT

func (tc *TestClient) PUT(path string) *TestRequestBuilder

type TestRequestBuilder

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

func DELETE

func DELETE(t *testing.T, url string) *TestRequestBuilder

func Expect

func Expect(t *testing.T, rb *RequestBuilder) *TestRequestBuilder

Expect wraps a RequestBuilder for test assertions.

func FromRecorder

func FromRecorder(t *testing.T, recorder interface{ Result() *http.Response }) *TestRequestBuilder

FromRecorder creates a TestRequestBuilder from a httptest.ResponseRecorder. This is useful for testing handlers directly without making HTTP requests.

func GET

func GET(t *testing.T, url string) *TestRequestBuilder
func HEAD(t *testing.T, url string) *TestRequestBuilder

func OPTIONS

func OPTIONS(t *testing.T, url string) *TestRequestBuilder

func PATCH

func PATCH(t *testing.T, url string) *TestRequestBuilder

func POST

func POST(t *testing.T, url string) *TestRequestBuilder

func PUT

func PUT(t *testing.T, url string) *TestRequestBuilder

func Request

func Request(t *testing.T) *TestRequestBuilder

Request creates a new TestRequestBuilder for building test assertions.

func (*TestRequestBuilder) Body

func (*TestRequestBuilder) Execute

func (trb *TestRequestBuilder) Execute() (*http.Response, []byte)

Execute performs the request and returns the raw response for manual inspection.

func (*TestRequestBuilder) ExpectBody

func (trb *TestRequestBuilder) ExpectBody(expected string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectBodyContains

func (trb *TestRequestBuilder) ExpectBodyContains(substr string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectBodyNotContains

func (trb *TestRequestBuilder) ExpectBodyNotContains(substr string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectContains

func (trb *TestRequestBuilder) ExpectContains(substr string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectContentType

func (trb *TestRequestBuilder) ExpectContentType(contentType string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectCookie

func (trb *TestRequestBuilder) ExpectCookie(key, expectedValue string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectCookieExist

func (trb *TestRequestBuilder) ExpectCookieExist(key string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectEmptyBody

func (trb *TestRequestBuilder) ExpectEmptyBody() *TestRequestBuilder

func (*TestRequestBuilder) ExpectHeader

func (trb *TestRequestBuilder) ExpectHeader(key, value string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectHeaderContains

func (trb *TestRequestBuilder) ExpectHeaderContains(key, substr string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectHeaderExists

func (trb *TestRequestBuilder) ExpectHeaderExists(key string) *TestRequestBuilder

func (*TestRequestBuilder) ExpectJSON

func (trb *TestRequestBuilder) ExpectJSON(expected any) *TestRequestBuilder

func (*TestRequestBuilder) ExpectJSONPath

func (trb *TestRequestBuilder) ExpectJSONPath(path string, expected any) *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatus

func (trb *TestRequestBuilder) ExpectStatus(code int) *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusAccepted

func (trb *TestRequestBuilder) ExpectStatusAccepted() *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusBadRequest

func (trb *TestRequestBuilder) ExpectStatusBadRequest() *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusConflict

func (trb *TestRequestBuilder) ExpectStatusConflict() *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusCreated

func (trb *TestRequestBuilder) ExpectStatusCreated() *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusForbidden

func (trb *TestRequestBuilder) ExpectStatusForbidden() *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusInternalServerError

func (trb *TestRequestBuilder) ExpectStatusInternalServerError() *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusNoContent

func (trb *TestRequestBuilder) ExpectStatusNoContent() *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusNotFound

func (trb *TestRequestBuilder) ExpectStatusNotFound() *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusOK

func (trb *TestRequestBuilder) ExpectStatusOK() *TestRequestBuilder

func (*TestRequestBuilder) ExpectStatusUnauthorized

func (trb *TestRequestBuilder) ExpectStatusUnauthorized() *TestRequestBuilder

func (*TestRequestBuilder) FileUpload

func (trb *TestRequestBuilder) FileUpload(fieldName, filePath string) *TestRequestBuilder

func (*TestRequestBuilder) FormBody

func (trb *TestRequestBuilder) FormBody(values map[string]string) *TestRequestBuilder

func (*TestRequestBuilder) Header

func (trb *TestRequestBuilder) Header(k, v string) *TestRequestBuilder

func (*TestRequestBuilder) Headers

func (trb *TestRequestBuilder) Headers(headers map[string]string) *TestRequestBuilder

func (*TestRequestBuilder) JSONBody

func (trb *TestRequestBuilder) JSONBody(v any) *TestRequestBuilder

func (*TestRequestBuilder) Method

func (trb *TestRequestBuilder) Method(method string) *TestRequestBuilder

func (*TestRequestBuilder) MultipartBody

func (trb *TestRequestBuilder) MultipartBody(fields map[string]string, files ...FileField) *TestRequestBuilder

func (*TestRequestBuilder) ParseJSON

func (trb *TestRequestBuilder) ParseJSON(target any) *TestRequestBuilder

func (*TestRequestBuilder) Path

func (trb *TestRequestBuilder) Path(path string) *TestRequestBuilder

func (*TestRequestBuilder) QueryParam

func (trb *TestRequestBuilder) QueryParam(key, value string) *TestRequestBuilder

func (*TestRequestBuilder) QueryParams

func (trb *TestRequestBuilder) QueryParams(params map[string]string) *TestRequestBuilder

func (*TestRequestBuilder) SetBasicAuth

func (trb *TestRequestBuilder) SetBasicAuth(username, password string) *TestRequestBuilder

func (*TestRequestBuilder) SetBearerAuth

func (trb *TestRequestBuilder) SetBearerAuth(token string) *TestRequestBuilder

func (*TestRequestBuilder) SetCookie

func (trb *TestRequestBuilder) SetCookie(name, value string) *TestRequestBuilder

func (*TestRequestBuilder) SetCookies

func (trb *TestRequestBuilder) SetCookies(cookies []*http.Cookie) *TestRequestBuilder

func (*TestRequestBuilder) Timeout

func (trb *TestRequestBuilder) Timeout(timeout time.Duration) *TestRequestBuilder

func (*TestRequestBuilder) URL

func (*TestRequestBuilder) WithHTTPClient

func (trb *TestRequestBuilder) WithHTTPClient(client *http.Client) *TestRequestBuilder

Jump to

Keyboard shortcuts

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