mockaso

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Jan 11, 2026 License: GPL-3.0 Imports: 16 Imported by: 0

README

Mockaso

A lightweight Go library for creating HTTP mock servers in tests. Built around Go's httptest.Server, Mockaso provides a fluent API for stubbing HTTP endpoints with powerful matching rules and flexible responses.

Features

  • Fluent API: Chain methods for readable and maintainable test code
  • Flexible Matching: Match requests by method, URL, headers, body, query parameters, and more
  • Pattern Matching: Extract URL parameters with patterns like /users/{id} and use them in matchers
  • Multiple URL Matching Strategies: Exact match, regex, and pattern-based matching
  • JSON Support: Built-in JSON response handling with automatic Content-Type headers
  • Response Delays: Simulate network latency with configurable delays
  • Relative URLs: Use relative paths in tests without worrying about server addresses
  • Thread-Safe: Safely add and clear stubs from concurrent tests
  • Order-Based Matching: First matching stub wins, allowing specific-to-general stub ordering
  • Optional Logging: Integrate with testing.T, slog, or log for debugging

Installation

go get github.com/royhq/mockaso

Quick Start

package mypackage_test

import (
    "testing"
    "github.com/roycodev/mockaso"
)

func TestAPIClient(t *testing.T) {
    // Create and start mock server
    server := mockaso.NewServer(mockaso.WithLogger(t))
    server.MustStart()
    defer server.MustShutdown()

    // Stub an endpoint
    server.Stub("GET", mockaso.Path("/api/users")).
        Respond(
            mockaso.WithJSON([]map[string]any{
                {"id": 1, "name": "Alice"},
                {"id": 2, "name": "Bob"},
            }),
            mockaso.WithStatusCode(200),
        )

    // Use the server in your tests
    client := server.Client()
    resp, err := client.Get("/api/users")

    // ... your assertions
}

Usage Examples

Basic Stub
server.Stub("POST", mockaso.Path("/api/users")).
    Respond(
        mockaso.WithStatusCode(201),
        mockaso.WithBody([]byte(`{"id": 1, "name": "Alice"}`)),
    )
Match Request Headers
server.Stub("GET", mockaso.Path("/api/users")).
    Match(mockaso.MatchHeader("Authorization", "Bearer token123")).
    Respond(mockaso.WithJSON(users))
Match Request Body
server.Stub("POST", mockaso.Path("/api/users")).
    Match(mockaso.MatchBody([]byte(`{"name":"Alice"}`))).
    Respond(mockaso.WithStatusCode(201))
URL Pattern with Parameters
// Match /users/123, /users/456, etc.
server.Stub("GET", mockaso.PathPattern("/users/{id}")).
    Match(mockaso.MatchParam("id", "123")).
    Respond(mockaso.WithJSON(map[string]any{"id": 123, "name": "Alice"}))
Regex Matching
// Match any UUID in the path
server.Stub("GET", mockaso.PathRegex(`/users/[0-9a-f-]+`)).
    Respond(mockaso.WithJSON(user))
Query Parameter Matching
server.Stub("GET", mockaso.Path("/api/users")).
    Match(mockaso.MatchQuery("role", "admin")).
    Respond(mockaso.WithJSON(adminUsers))
Response Delay
// Simulate slow network
server.Stub("GET", mockaso.Path("/api/slow")).
    Respond(
        mockaso.WithJSON(data),
        mockaso.WithDelay(2 * time.Second),
    )
Multiple Stubs (Order Matters)
// Specific case first
server.Stub("GET", mockaso.Path("/api/users/special")).
    Respond(mockaso.WithJSON(specialUser))

// General case second
server.Stub("GET", mockaso.PathPattern("/api/users/{id}")).
    Respond(mockaso.WithJSON(regularUser))
Clearing Stubs
func TestMultipleCases(t *testing.T) {
    server := mockaso.NewServer()
    server.MustStart()
    defer server.MustShutdown()

    t.Run("case1", func(t *testing.T) {
        server.Stub("GET", mockaso.Path("/api/data")).
            Respond(mockaso.WithJSON(data1))
        // ... test logic
        server.Clear() // Clean up for next test
    })

    t.Run("case2", func(t *testing.T) {
        server.Stub("GET", mockaso.Path("/api/data")).
            Respond(mockaso.WithJSON(data2))
        // ... test logic
    })
}

How It Works

  1. Create a server: Wraps Go's httptest.Server with stub management
  2. Add stubs: Define expected requests and their responses
  3. First match wins: Stubs are evaluated in order; the first matching stub serves the response
  4. No match: Returns HTTP 666 (demon code) when no stub matches
  5. Use in tests: Access via server.Client() for relative URLs or server.URL() for full address

License

MIT License - see LICENSE file for details

Documentation

Overview

Package mockaso provides a fluent API for creating HTTP mock servers in tests. It wraps Go's httptest.Server with a powerful stubbing system that supports request matching, pattern-based URLs, and flexible response configuration.

Basic Usage

Create a server, add stubs, and use it in your tests:

server := mockaso.NewServer(mockaso.WithLogger(t))
server.MustStart()
defer server.MustShutdown()

server.Stub("GET", mockaso.Path("/users")).
	Respond(mockaso.WithJSON([]User{{ID: 1, Name: "Alice"}}))

client := server.Client()
resp, _ := client.Get("/users") // Uses relative URL

Stub Matching

Stubs are evaluated in the order they are added. The first matching stub handles the request. If no stub matches, the server returns status 666.

URL matching supports exact paths, regex patterns, and parameter extraction:

mockaso.Path("/users")                    // Exact path
mockaso.PathRegex(`/users/\d+`)          // Regex
mockaso.PathPattern("/users/{id}")       // Pattern with params

Additional matchers refine which requests a stub handles:

server.Stub("POST", mockaso.Path("/users")).
	Match(
		mockaso.MatchHeader("Content-Type", "application/json"),
		mockaso.MatchJSONBody(expectedData),
	).
	Respond(mockaso.WithStatusCode(201))

Response Configuration

Responses support JSON, custom headers, status codes, and delays:

stub.Respond(
	mockaso.WithStatusCode(200),
	mockaso.WithJSON(data),
	mockaso.WithHeader("X-Custom", "value"),
	mockaso.WithDelay(100 * time.Millisecond),
)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BodyMatcherMapFunc

type BodyMatcherMapFunc func(map[string]any) bool

BodyMatcherMapFunc is a function that validates an HTTP request body represented as a map. It receives the parsed JSON body and returns true if the request should match.

type BodyMatcherStringFunc

type BodyMatcherStringFunc func(string) bool

BodyMatcherStringFunc is a function that validates an HTTP request body as a plain string. It receives the raw body content and returns true if the request should match.

type LogLogger

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

LogLogger implementation of Logger using a log.Logger. This is useful for integration with standard library logging or legacy code.

Example:

logger := log.New(os.Stdout, "[MOCKASO] ", log.LstdFlags)
server := mockaso.NewServer(mockaso.WithLogLogger(logger))

func NewLogLogger

func NewLogLogger(logger *log.Logger) *LogLogger

NewLogLogger creates a new LogLogger wrapping the provided log.Logger.

func (*LogLogger) Log

func (l *LogLogger) Log(args ...any)

func (*LogLogger) Logf

func (l *LogLogger) Logf(format string, args ...any)

type Logger

type Logger interface {
	Log(...any)
	Logf(string, ...any)
}

Logger abstraction intended for use with testing.T. The interface is compatible with testing.T's Log and Logf methods, allowing test loggers to be used directly with mock servers.

Example with testing.T:

func TestAPI(t *testing.T) {
	server := mockaso.NewServer(mockaso.WithLogger(t))
	server.MustStart()
	defer server.MustShutdown()
	// Server will log to test output
}

type RequestMatcherFunc

type RequestMatcherFunc func(*http.Request) bool

RequestMatcherFunc is a function that determines if an HTTP request matches specific criteria. It is used to create custom matchers with MatchRequest.

Example:

customMatcher := mockaso.RequestMatcherFunc(func(r *http.Request) bool {
	return r.ContentLength > 1024
})
stub.Match(mockaso.MatchRequest(customMatcher))

type Server

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

Server is a wrapper around httptest.Server that provides HTTP endpoint mocking capabilities with a fluent API for stubbing responses.

When no stub matches an incoming request, the server responds with status code 666 (demon code) and a descriptive error message.

Example:

server := mockaso.NewServer(mockaso.WithLogger(t))
server.MustStart()
defer server.MustShutdown()

server.Stub("GET", mockaso.Path("/users")).
	Respond(
		mockaso.WithJSON([]User{{ID: 1, Name: "Alice"}}),
		mockaso.WithStatusCode(200),
	)

client := server.Client()
resp, _ := client.Get("/users")

func MustStartNewServer

func MustStartNewServer(opts ...ServerOption) *Server

MustStartNewServer creates a new mock server and starts it immediately. This is a convenience function equivalent to calling NewServer followed by MustStart. It panics if the server fails to start.

Example:

server := mockaso.MustStartNewServer(mockaso.WithLogger(t))
defer server.MustShutdown()

func NewServer

func NewServer(opts ...ServerOption) *Server

NewServer creates a new mock server with the given options. The server is not started automatically - call Start or MustStart to begin accepting requests.

By default, the server uses a silent logger that produces no output. Use WithLogger, WithSlogLogger, or WithLogLogger to enable logging.

Example:

server := mockaso.NewServer(mockaso.WithLogger(t))
server.MustStart()
defer server.MustShutdown()

func (*Server) Clear

func (s *Server) Clear()

Clear removes all registered stubs from the server. The server continues running but will return 666 for all requests until new stubs are added. This method is thread-safe.

func (*Server) Client

func (s *Server) Client() *http.Client

Client returns an HTTP client configured to work with this mock server. The returned client uses a custom transport that automatically resolves relative URLs (like "/api/users") to the server's base URL.

Example:

client := server.Client()
resp, err := client.Get("/api/users") // Automatically uses server.URL()

func (*Server) Logger

func (s *Server) Logger() Logger

Logger returns the configured logger for this server.

func (*Server) MustShutdown

func (s *Server) MustShutdown()

MustShutdown stops the server and panics if an error occurs. This is a convenience method for tests, typically used with defer.

func (*Server) MustStart

func (s *Server) MustStart()

MustStart starts the server and panics if an error occurs. This is a convenience method for tests where startup failure should be fatal.

func (*Server) Shutdown

func (s *Server) Shutdown() error

Shutdown stops the HTTP test server and cleans up resources. It is safe to call Shutdown on an already stopped server. Returns nil on success.

func (*Server) Start

func (s *Server) Start() error

Start initializes and starts the HTTP test server. It is safe to call Start multiple times - subsequent calls are no-ops. Returns nil on success.

func (*Server) Stub

func (s *Server) Stub(method string, url URLMatcher) Stub

Stub creates and registers a new stub for the given HTTP method and URL matcher. Stubs are evaluated in the order they are added - the first matching stub handles the request.

The method parameter should be an HTTP method like "GET", "POST", "PUT", etc. The url parameter is a URLMatcher that determines which requests this stub handles.

Example:

server.Stub("GET", mockaso.Path("/users")).
	Match(mockaso.MatchHeader("Accept", "application/json")).
	Respond(mockaso.WithJSON(users), mockaso.WithStatusCode(200))

func (*Server) TestServer

func (s *Server) TestServer() *httptest.Server

TestServer returns the underlying httptest.Server. This is useful for advanced scenarios that require direct access to the test server.

func (*Server) URL

func (s *Server) URL() string

URL returns the base URL of the running server. Returns an empty string if the server has not been started.

Example:

server.MustStart()
fmt.Println(server.URL()) // http://127.0.0.1:12345

type ServerOption

type ServerOption func(*Server)

ServerOption is a function that configures a Server during initialization. Options are applied in the order they are provided to NewServer.

func WithLogLogger

func WithLogLogger(logger *log.Logger) ServerOption

WithLogLogger sets a Logger from log.Logger.

Example:

logger := log.New(os.Stdout, "[MOCKASO] ", log.LstdFlags)
server := mockaso.NewServer(mockaso.WithLogLogger(logger))

func WithLogger

func WithLogger(logger Logger) ServerOption

WithLogger sets a Logger. Intended for use with testing.T.

Example:

func TestAPI(t *testing.T) {
	server := mockaso.NewServer(mockaso.WithLogger(t))
	// Server will log to test output
}

func WithSlogLogger

func WithSlogLogger(logger *slog.Logger, level slog.Level) ServerOption

WithSlogLogger sets a Logger from slog.Logger. level is the slog.LogLevel that will be used.

Example:

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
server := mockaso.NewServer(mockaso.WithSlogLogger(logger, slog.LevelInfo))

type SlogLogger

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

SlogLogger implementation of Logger using an slog.Logger. All log messages are emitted at the configured level.

Example:

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
server := mockaso.NewServer(
	mockaso.WithSlogLogger(logger, slog.LevelInfo),
)

func NewSlogLogger

func NewSlogLogger(logger *slog.Logger, level slog.Level) *SlogLogger

NewSlogLogger creates a new SlogLogger with the specified logger and level. All messages logged through this logger will use the given level.

func (*SlogLogger) Log

func (l *SlogLogger) Log(args ...any)

func (*SlogLogger) Logf

func (l *SlogLogger) Logf(format string, args ...any)

type Stub

type Stub interface {
	StubResponder
	Match(...StubMatcherRule) StubResponder
}

Stub represents a configured HTTP endpoint mock with request matching rules and response configuration. It provides a fluent API for chaining matchers and response rules.

The typical usage pattern is:

  1. Create a stub with Server.Stub(method, urlMatcher)
  2. Optionally add matchers with Match(...)
  3. Configure the response with Respond(...)

Example:

server.Stub("POST", mockaso.Path("/users")).
	Match(
		mockaso.MatchHeader("Content-Type", "application/json"),
		mockaso.MatchJSONBody(map[string]string{"name": "Alice"}),
	).
	Respond(
		mockaso.WithStatusCode(201),
		mockaso.WithJSON(map[string]any{"id": 123, "name": "Alice"}),
	)

type StubMatcherRule

type StubMatcherRule func() requestMatcherFunc

StubMatcherRule is a function that returns a request matcher. Multiple StubMatcherRules can be applied to a stub, and ALL must return true for the stub to match the request.

Matchers are evaluated after the URL and method match, in the order they were added to the stub.

Example:

server.Stub("GET", mockaso.Path("/users")).
	Match(
		mockaso.MatchHeader("Authorization", "Bearer token"),
		mockaso.MatchQuery("active", "true"),
	)

func MatchBodyMapFunc

func MatchBodyMapFunc(bodyMatcher BodyMatcherMapFunc) StubMatcherRule

MatchBodyMapFunc sets a rule to match the http request with the given matcher based on the body as a map. The matcher is a func that receives the body parameters as a map. If the body is empty the map will be empty.

This is useful for partial matching or complex validation logic.

Example:

server.Stub("POST", mockaso.Path("/users")).
	Match(mockaso.MatchBodyMapFunc(func(body map[string]any) bool {
		name, ok := body["name"].(string)
		return ok && len(name) > 0
	}))

func MatchBodyStringFunc

func MatchBodyStringFunc(bodyMatcher BodyMatcherStringFunc) StubMatcherRule

MatchBodyStringFunc sets a rule to match the http request with the given matcher based on the body as string. The matcher is a func that receives the body as plain text.

Example:

server.Stub("POST", mockaso.Path("/webhook")).
	Match(mockaso.MatchBodyStringFunc(func(body string) bool {
		return strings.Contains(body, "event=created")
	}))

func MatchHeader

func MatchHeader(key, value string) StubMatcherRule

MatchHeader sets a rule to match the http request with the given header value. The match is case-sensitive for the value, but header names follow HTTP canonicalization rules.

Example:

server.Stub("GET", mockaso.Path("/api")).
	Match(mockaso.MatchHeader("Authorization", "Bearer secret-token"))

func MatchJSONBody

func MatchJSONBody(body any) StubMatcherRule

MatchJSONBody sets a rule to match the http request with the given JSON body. The specified body will be marshaled and compared with the actual request body. JSON comparison is structural, so field order and whitespace are ignored.

Example:

expectedUser := User{Name: "Alice", Age: 30}
server.Stub("POST", mockaso.Path("/users")).
	Match(mockaso.MatchJSONBody(expectedUser))

func MatchNoBody

func MatchNoBody() StubMatcherRule

MatchNoBody sets a rule to match the http request with empty body.

Example:

server.Stub("GET", mockaso.Path("/ping")).
	Match(mockaso.MatchNoBody())

func MatchParam

func MatchParam(key, value string) StubMatcherRule

MatchParam sets a rule to match the http request with the given path param value. This requires that the URL was specified with URLPattern or PathPattern.

Example:

server.Stub("GET", mockaso.PathPattern("/users/{id}")).
	Match(mockaso.MatchParam("id", "123"))

This matches: GET /users/123

func MatchQuery

func MatchQuery(key, value string) StubMatcherRule

MatchQuery sets a rule to match the http request with the given query string value. Only the first value is checked if the query parameter appears multiple times.

Example:

server.Stub("GET", mockaso.Path("/users")).
	Match(mockaso.MatchQuery("status", "active"))

This matches: GET /users?status=active

func MatchRawJSONBody

func MatchRawJSONBody[T string | []byte | json.RawMessage](raw T) StubMatcherRule

MatchRawJSONBody sets a rule to match the http request with the given raw JSON body. The JSON is compared structurally, so formatting differences are ignored.

Example:

server.Stub("POST", mockaso.Path("/users")).
	Match(mockaso.MatchRawJSONBody(`{"name":"Alice","age":30}`))

func MatchRequest

func MatchRequest(requestMatcher RequestMatcherFunc) StubMatcherRule

MatchRequest sets a rule to match the http request given a custom matcher. This is the most flexible matching option and allows for arbitrary request validation.

Example:

server.Stub("GET", mockaso.Path("/api")).
	Match(mockaso.MatchRequest(func(r *http.Request) bool {
		return r.Header.Get("User-Agent") != ""
	}))

type StubResponder

type StubResponder interface {
	Respond(...StubResponseRule)
}

StubResponder provides the final step in the fluent API for configuring a stub's response. After all matchers are defined, call Respond to set the HTTP response that will be returned when the stub matches.

Example:

stub.Respond(
	mockaso.WithStatusCode(200),
	mockaso.WithJSON(responseData),
	mockaso.WithHeader("X-Custom", "value"),
)

type StubResponseRule

type StubResponseRule func(*stubResponse)

StubResponseRule is a function that configures a stub's HTTP response. Multiple rules can be applied to a single response, and they are processed in the order they are provided to Respond.

Example:

stub.Respond(
	mockaso.WithStatusCode(201),
	mockaso.WithJSON(createdResource),
	mockaso.WithHeader("Location", "/api/resources/123"),
)

func WithBody

func WithBody(body any) StubResponseRule

WithBody sets the response body. Accepts []byte, string, json.RawMessage, or io.Reader. For other types, the value is converted using fmt.Sprintf("%v", value).

Example:

stub.Respond(mockaso.WithBody("Hello, World!"))
stub.Respond(mockaso.WithBody([]byte{0x89, 0x50, 0x4E, 0x47}))

func WithDelay

func WithDelay(d time.Duration) StubResponseRule

WithDelay sets a delay time to the response. The delay is applied before writing the response body. If the request context is cancelled during the delay, the response is aborted.

Example:

stub.Respond(
	mockaso.WithJSON(data),
	mockaso.WithDelay(500 * time.Millisecond), // Simulate slow API
)

func WithHeader

func WithHeader(key, value string) StubResponseRule

WithHeader sets a response header. If the key already exists it will be overwritten.

Example:

stub.Respond(
	mockaso.WithHeader("Content-Type", "text/plain"),
	mockaso.WithHeader("X-Request-ID", "abc-123"),
)

func WithHeaders

func WithHeaders(headers map[string]string) StubResponseRule

WithHeaders sets a set of response headers. These headers will be added to the already specified headers. If any key already exists it will be overwritten.

Example:

headers := map[string]string{
	"X-RateLimit-Limit": "100",
	"X-RateLimit-Remaining": "99",
}
stub.Respond(mockaso.WithHeaders(headers))

func WithJSON

func WithJSON(body any) StubResponseRule

WithJSON sets the response content with the marshal output of the given body. The response will include the Content-Type:application/json header. Panics if the body cannot be marshaled to JSON.

Example:

user := User{ID: 1, Name: "Alice"}
stub.Respond(mockaso.WithJSON(user))

// Or with inline data
stub.Respond(mockaso.WithJSON(map[string]any{
	"users": []string{"Alice", "Bob"},
	"total": 2,
}))

func WithRawJSON

func WithRawJSON[T string | []byte | json.RawMessage](raw T) StubResponseRule

WithRawJSON sets the response content with the given JSON. The response will include the Content-Type:application/json header. The provided JSON is validated before being set.

Example:

stub.Respond(mockaso.WithRawJSON(`{"status":"ok","count":42}`))

func WithStatusCode

func WithStatusCode(statusCode int) StubResponseRule

WithStatusCode sets the response status code. If not specified, the default status code is 200 (OK).

Example:

stub.Respond(mockaso.WithStatusCode(404))

type URLMatcher

type URLMatcher func(*url.URL, *stub) bool

URLMatcher is a function that matches against a request URL. It is the primary matching mechanism for stubs and is evaluated before any StubMatcherRule functions.

URLMatchers can extract parameters from URL patterns and store them in the stub for later use with MatchParam.

func Path

func Path(path string) URLMatcher

Path will match http request when the value specified is equals to the request URL path part.

Example:

server.Stub("GET", mockaso.Path("/api/users"))

func PathPattern

func PathPattern(pattern string) URLMatcher

PathPattern will match http request when the given URL pattern match to the request URL path part. Can specify path params with {param_name} notation and then use it in matcher. Can't use parameters in query string, only path will be evaluated.

Example:

PathPattern("/api/users/{user_id}")

func PathRegex

func PathRegex(pattern string) URLMatcher

PathRegex will match http request when the regex pattern specified match to the request URL path part.

Example:

server.Stub("GET", mockaso.PathRegex(`^/users/\d+$`))

func URL

func URL(u string) URLMatcher

URL will match http request when the value specified is equals to the full request URL.

Example:

server.Stub("GET", mockaso.URL("http://example.com/api/users"))

func URLPattern

func URLPattern(pattern string) URLMatcher

URLPattern will match http request when the given URL pattern match to the request URL. Can specify path params with {param_name} notation and then use it in matcher. Can use parameters in query string.

Example:

URLPattern("/api/users/{user_id}")
URLPattern("/api/users/{user_id}?attrs={attrs}")

func URLRegex

func URLRegex(pattern string) URLMatcher

URLRegex will match http request when the regex pattern specified match to the request URL.

Example:

server.Stub("GET", mockaso.URLRegex(`^http://example\.com/users/\d+$`))

Jump to

Keyboard shortcuts

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