Help us shape the future of Go! Take the 2026 Go Developer Survey

vlatte

package module
v0.1.0-rc.1 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 29 Imported by: 0

README

VLatte

Mock HTTP and gRPC servers for Go tests,
and a CLI that serves them from files.

Release CI Docs codecov Quality gate status Go Reference

About

VLatte starts real HTTP and gRPC servers that answer the way you tell them to. Start one in a Go test with mockhttp or mockgrpc, or run the vlatte CLI to serve mocks from YAML or JSON files. Either way, mocks match requests on any part of them and reply with static or templated answers.

Read the documentation

Features

  • HTTP and gRPC mock servers. gRPC needs no service implementation: unary, server, client and bidi streaming calls, from generated code or descriptor sets.
  • Matchers shared by every protocol: equality, JSON path, JSON Schema, XPath, regex, globs, dates and combinators, plus your own.
  • Replies with templated headers and bodies, sequences, random picks, echo, multipart and custom functions.
  • Mock files in YAML or JSON, with JSON schemas for editors, reloaded on demand.
  • Scenarios for stateful flows, and webhooks sent after a mock answers.
  • Proxying, and recording of a real service to play it back later, with outbound rules that limit the addresses a server connects to.
  • Fault injection: latency, slow bandwidth, connection resets, malformed and empty answers.
  • A request journal to verify what clients sent, and debug headers on every answer.
  • An admin API to manage mocks while a server runs, with Basic or bearer authentication and TLS, also served over MCP for AI agents.
  • A CLI and a Docker image to run mock servers outside Go.

Install

The library, for Go 1.27 or later:

go get github.com/caffeine-projects/vlatte

The CLI, with Homebrew, an archive from GitHub Releases, Go, or Docker:

brew install caffeine-projects/tap/vlatte
go install github.com/caffeine-projects/vlatte/cmd/vlatte@latest
docker run --rm ghcr.io/caffeine-projects/vlatte:v0.1.0-rc.1 version

Release archives and images are signed: see Verifying a release.

Until v1.0.0, a minor version may change the API.

Quick start

HTTP in a test

func TestCreateUser(t *testing.T) {
	m := mockhttp.MustNew()
	m.MustStart()
	t.Cleanup(m.Close)

	created := m.MustMock(mockhttp.Postf("/users").
		Header("Authorization", match.HasPrefix("Bearer ")).
		Body(match.JSONPath("$.name", match.Equal("ann"))).
		Reply(mockhttp.Created().
			HeaderTemplate("Location", `/users/{{ uuid }}`).
			ContentType("application/json").
			BodyTemplate(`{"name": {{ (fromJSON .Request.Body.String).name | toJSON }}}`)))

	// Point your client at m.URL() and call it.
	user, err := users.NewClient(m.URL(), "token").Create(t.Context(), "ann")
	// 201, Location: /users/<uuid>, body {"name": "ann"}

	created.AssertNumberOfCalls(t, 1)
}

gRPC in a test

func TestGreeter(t *testing.T) {
	m := mockgrpc.MustNew(mockgrpc.Setup().InMemory()) // no port: only m.Dial reaches it
	m.MustStart()
	t.Cleanup(m.Close)

	m.MustMock(mockgrpc.Method("Greeter/SayHello").
		Field("name", match.HasPrefix("a")).
		Reply(mockgrpc.Template(`{"message": "hi {{ .Request.Message.name }}"}`)))

	m.MustMock(mockgrpc.Method("Greeter/Watch").
		Reply(mockgrpc.Messages(&pb.Event{Id: "1"}, &pb.Event{Id: "2"}).
			MessageDelay(100 * time.Millisecond).
			Status(codes.Unavailable, "restarting")))

	client := pb.NewGreeterClient(m.MustDial()) // closed with the server
	res, err := client.SayHello(t.Context(), &pb.HelloRequest{Name: "ann"})
	// res.Message == "hi ann"; Watch sends 1, 2, then Unavailable
}

CLI with a mock file

# testdata/vlatte/_mocks/users_mock.yaml
request:
  method: GET
  path_template: /users/{id}
  path_params:
    id: [regex, '^[0-9]+$']
  header:
    authorization: [hasprefix, "Bearer "]
response:
  status: 200
  header:
    content-type: application/json
  body: '{"id": {{ .Request.PathParams.id }}, "name": "ann"}'
  template:
    enabled: true
delay: 150ms
vlatte --addr=127.0.0.1:8080
curl -H 'Authorization: Bearer x' http://127.0.0.1:8080/users/7
# {"id": 7, "name": "ann"}

Documentation

The documentation covers each protocol, the shared features, the CLI and Docker, and the mock file and admin API references. The Go API is on pkg.go.dev.

Development

make test   # run the tests with the race detector
make lint   # run golangci-lint

License

MIT

Documentation

Overview

Package vlatte holds what every vlatte mock server shares: mocks, scopes, matchers, delays, events, recipes and assertions.

Tests usually import a protocol package instead:

and github.com/caffeine-projects/vlatte/match for request matchers.

Index

Constants

View Source
const (
	FailureNoMatch = "no-match" // no mock matched the request
	FailureMatch   = "match"    // finding a mock failed: a matcher or the store returned an error
	FailureReply   = "reply"    // a mock matched, and its reply failed
	FailurePanic   = "panic"    // serving the request panicked
)

Values of HeaderFailure.

View Source
const (
	HeaderFailure           = "VLatte-Error"
	HeaderMatchedMockID     = "VLatte-Matched-Mock-Id"     // ID of the mock that matched
	HeaderMatchedMockName   = "VLatte-Matched-Mock-Name"   // name of the mock that matched, when it has one
	HeaderMatchedMockHits   = "VLatte-Matched-Mock-Hits"   // hits of the mock that matched, this request included
	HeaderMatchedMockSource = "VLatte-Matched-Mock-Source" // file the mock that matched was loaded from (HTTP)
	HeaderRequestID         = "VLatte-Request-Id"          // ID of the request, as logs, templates and the journal see it (HTTP; gRPC with a journal)
	HeaderProxied           = "VLatte-Proxied"             // "true" when a proxy answered instead of a mock (HTTP)
)

Headers (HTTP) or metadata keys (gRPC) a server adds to its answers. gRPC sends them lowercased, as metadata keys are.

View Source
const (
	LogLevelDebug = LogLevel(zerolog.DebugLevel)
	LogLevelInfo  = LogLevel(zerolog.InfoLevel)
	LogLevelWarn  = LogLevel(zerolog.WarnLevel)
	LogLevelError = LogLevel(zerolog.ErrorLevel)
	// LogLevelNone writes only the entries that have no level.
	LogLevelNone = LogLevel(zerolog.NoLevel)
	// LogLevelDisabled writes nothing.
	LogLevelDisabled = LogLevel(zerolog.Disabled)
)

Log levels. The default logger writes the entries at its level and above.

Variables

View Source
var (
	// ErrMatchPanicked is wrapped by the error of a match whose matcher panicked, such as that of [Eval.Match], with
	// the panic value.
	ErrMatchPanicked = errors.New("mock: panic")

	// ErrMatching is wrapped by the error of [Mocks.Find] when a matcher fails or panics, with the name of its mock.
	ErrMatching = errors.New("mock: matching")
)
View Source
var (
	// ErrAbort is the cause of a flow a stage aborted, with [Flow.Abort] or by returning an error that matches it.
	// The server cuts the stream: an HTTP/1 connection is closed, and an HTTP/2 stream is reset.
	ErrAbort = errors.New("flow: aborted")

	// ErrReset aborts a flow, as [ErrAbort] does, with a TCP reset where the connection allows one: its unsent data
	// is dropped and the peer reads a reset, not an end. Elsewhere, such as over HTTP/2, it cuts the stream.
	ErrReset = errors.New("flow: reset")
)
View Source
var (
	// ErrRecording is returned when a recording starts while another one runs.
	ErrRecording = errors.New("recorder: already recording")

	// ErrNotRecording is returned when there is no recording to stop.
	ErrNotRecording = errors.New("recorder: not recording")
)
View Source
var (
	// ErrStarted is returned when a server that started starts again: a server starts once.
	ErrStarted = errors.New("server: already started")

	// ErrOutboundRefused is the error of a connection a server refused to open, to an address its outbound rules
	// refuse: see OutboundAllow in [github.com/caffeine-projects/vlatte/mockhttp.Config] and in
	// [github.com/caffeine-projects/vlatte/mockgrpc.Config].
	ErrOutboundRefused = errors.New("server: the address is refused by the outbound rules")
)
View Source
var (
	// ErrNotFound is returned by [Store.Pick] when no mock with the given ID exists.
	ErrNotFound = errors.New("store: mock not found")

	// ErrDuplicateID is returned by [Store.Store] when the ID of a mock is already stored, or repeated in the call.
	ErrDuplicateID = errors.New("store: mock id already in use")
)
View Source
var ErrEventAlreadyRegistered = errors.New("vlatte: event already registered")

ErrEventAlreadyRegistered is returned by EventRegistry.Register when a listener has the name already.

View Source
var ErrTemplateDataKeyExists = errors.New("server: two template data providers have the same key")

ErrTemplateDataKeyExists says two template data providers of a server have the same key.

Functions

func CheckTemplateDataProviders

func CheckTemplateDataProviders[In any](providers []TemplateDataProvider[In]) error

CheckTemplateDataProviders returns ErrTemplateDataKeyExists when two providers have the same key: templates could read only one of them.

func NotCalled

func NotCalled[M Mock](t TestingT, inspector Inspector[M]) bool

NotCalled asserts that none of the mocks tracked by inspector were called.

func ProvideTemplateData

func ProvideTemplateData[In any](providers []TemplateDataProvider[In], in In) (map[string]any, error)

ProvideTemplateData returns the data providers give for in, by their key. It returns the error of the first that fails, which names it.

func Times

func Times[M Mock](t TestingT, inspector Inspector[M], expected int64) bool

Times asserts that the inspector recorded exactly expected hits.

Types

type Base

type Base[TM Mock, A any] struct {
	// contains filtered or unexported fields
}

Base is what every mock server shares: it adds mocks to its store in scopes, and asserts on them. A protocol's server embeds it.

func NewBase

func NewBase[TM Mock, A any](app A, repo Store[TM]) *Base[TM, A]

NewBase creates a Base that builds mocks for the server app and stores them in repo.

func (*Base[TM, A]) AssertCalled

func (app *Base[TM, A]) AssertCalled(t TestingT) bool

AssertCalled asserts that all mocks associated with this instance were called at least once.

func (*Base[TM, A]) AssertNotCalled

func (app *Base[TM, A]) AssertNotCalled(t TestingT) bool

AssertNotCalled asserts that none of the mocks associated with this instance were called.

func (*Base[TM, A]) AssertNumberOfCalls

func (app *Base[TM, A]) AssertNumberOfCalls(t TestingT, expected int64) bool

AssertNumberOfCalls asserts that the sum of matched request hits is equal to the given expected value.

func (*Base[TM, A]) Clean

func (app *Base[TM, A]) Clean(ctx context.Context) error

Clean removes all scoped mocks.

func (*Base[TM, A]) Disable

func (app *Base[TM, A]) Disable()

Disable disables all mocks.

func (*Base[TM, A]) Enable

func (app *Base[TM, A]) Enable()

Enable enables all mocks.

func (*Base[TM, A]) Hits

func (app *Base[TM, A]) Hits() int64

Hits returns the total matched request hits.

func (*Base[TM, A]) ListCalled

func (app *Base[TM, A]) ListCalled() []TM

ListCalled returns the mocks of every scope that were called at least once. It leaves out mocks stored outside every scope, such as those the admin API added.

func (*Base[TM, A]) ListPending

func (app *Base[TM, A]) ListPending() []TM

ListPending returns the mocks of every scope that were not called. It leaves out mocks stored outside every scope, such as those the admin API added.

func (*Base[TM, A]) Mock

func (app *Base[TM, A]) Mock(builders ...Builder[TM, A]) (*Scope[TM], error)

Mock adds one or multiple request mocks. A mock stored outside every scope, such as one the admin API added, gives way to a new mock with its ID. It returns a Scope instance that allows control of the added mocks and also checks if they were called or not.

func (*Base[TM, A]) MustMock

func (app *Base[TM, A]) MustMock(builders ...Builder[TM, A]) *Scope[TM]

MustMock adds one or multiple request mocks. It returns a Scope instance that allows control of the added mocks and also checks if they were called or not. It panics if any error occurs.

func (*Base[TM, A]) Remock

func (app *Base[TM, A]) Remock(ctx context.Context, old *Scope[TM], builders ...Builder[TM, A]) (*Scope[TM], error)

Remock builds builders and swaps their mocks for those of old in one step, so a request finds the mocks of old or the new ones, never neither, and returns their scope, which takes the place of old. With a nil old, it adds the mocks, as Mock does. A mock stored outside every scope, such as one the admin API added, gives way to a new mock with its ID. It changes nothing when a builder fails, when the store refuses the mocks, for example over an ID in use in another scope, or when old isn't tracked: replaced or unmocked already, or another server's.

func (*Base[TM, A]) RemockWith

func (app *Base[TM, A]) RemockWith(ctx context.Context, old *Scope[TM], swap Swap[TM], builders ...Builder[TM, A]) (*Scope[TM], error)

RemockWith does what Remock does, and swaps the mocks swap returns in the same step, so a request finds the mocks of old and those swap drops, or the new ones and those it adds, never a mix. It changes nothing when swap fails. A nil swap swaps nothing more.

func (*Base[TM, A]) Unmock

func (app *Base[TM, A]) Unmock(ctx context.Context, scope *Scope[TM]) error

Unmock deletes the mocks of scope and stops tracking it, so Hits, the mock lists and the assertions leave it out.

type Builder

type Builder[B Mock, TMockApp any] interface {
	Build(app TMockApp) (B, error)
}

Builder describes a Mock builder. Build returns a new mock on each call and must not change the builder, so a builder can be built for several servers, or more than once for one.

type Claimer

type Claimer interface {
	Claim(arg MatcherArg) bool
}

Claimer is a matcher whose mock takes a turn when Mocks.Find picks it, before the mock answers, such as the turn of a scenario's state. Claim takes the turn, and reports false when another request took it since the matcher matched: Find then looks for a mock again. Claim gets the argument Match gets, made again for the request, and the mock whose turn it takes, which MatcherArg.Mock returns. The next request sees the turn taken, even while the mock still answers, and the turn stays taken when the mock then fails to answer.

Match changes no state, so a report of a request no mock matched runs it. Claim reports false only when Match would now report false, or Find looks again forever, until its context ends. A mock has at most one Claimer: a turn taken stays taken when the next Claimer of the mock loses its own.

type CleanupT

type CleanupT interface {
	Cleanup(func())
}

CleanupT is an interface that allows for cleanup functions to be registered. Usually used in testing with testing.T.

type Configurer

type Configurer[TConfig any] interface {
	Apply(conf TConfig) error
}

Configurer lets users configure the Mock API.

type Delay

type Delay func() time.Duration

Delay picks how long a mock waits before it replies. It is called for each reply, so it can pick a different duration each time.

func Jitter

func Jitter(avg time.Duration, deviation float64) Delay

Jitter returns a Delay picked at random around avg, spread over deviation percent of avg: with an avg of 100ms and a deviation of 20, it picks from 90ms to just under 110ms. A deviation that leaves no spread, such as 0, gives avg every time.

func Latency

func Latency(latency time.Duration) Delay

Latency returns a Delay of latency every time.

func UniformDistribution

func UniformDistribution(minMillis, maxMillis int64) Delay

UniformDistribution returns a Delay picked at random from minMillis to maxMillis milliseconds, both included. It panics when maxMillis is lower than minMillis.

type Eval

type Eval[TIn any] struct {
	// Key describes to which specific part within the HTTP request parts a matcher is associated.
	// Eg.: a matcher for Header(Content-Type), the key would be "Content-Type".
	// A matcher can be associated to, for example the whole Header.
	// In this case, the key will be empty even if the matcher targets just one header name.
	Key string

	// Target is the index of the target of this evaluator.
	// Eg.: If this evaluator is targeting the header, the target should specify it, regardeless of which header name.
	// So, every evaluator targeting the header should have the same target value.
	Target int

	// Matcher associated with this [Eval].
	Matcher Matcher

	// Selector will extract the [http.Request] or a specific field of it and feed it to the associated [Matcher].
	Selector Selector[TIn]
}

Eval describes a matching evaluator. It contains all the necessary information to evaluate a match against something, eg.: the HTTP request or a piece of it. It also contains data to better describe mismatches.

func (*Eval[TIn]) Arg

func (e *Eval[TIn]) Arg(ctx context.Context, sub *Explanation, in TIn) MatcherArg

Arg returns the argument the matcher of e gets for in: the value its Selector selects, or nil without one, with an explanation under sub. A mock server that reads a matcher of the mock Find picked, such as a scenario's to journal its move, gives it the same argument.

func (*Eval[TIn]) Match

func (e *Eval[TIn]) Match(ctx context.Context, sub *Explanation, in TIn) (ok bool, err error)

Match evaluates the input against the matcher of this Eval.

func (*Eval[TIn]) String

func (e *Eval[TIn]) String() string

String returns a simple string representation of this Eval.

type Evaluators

type Evaluators[M Mock, TIn any] []*Eval[TIn]

Evaluators is a list of evaluators.

func (Evaluators[M, TIn]) Eval

func (e Evaluators[M, TIn]) Eval(ctx context.Context, sub *Explanation, in TIn) (bool, error)

Eval evaluates the input against all evaluators from a single Mock. Normally this function is used together with Mocks.Find

type EventArgs

type EventArgs interface {
	Kind() Kind
	Kit() Kit
	Parameters() Parameters
}

EventArgs is the common argument type for all events. Event-specific interfaces (e.g. OnComplete) extend this.

type EventConfig

type EventConfig struct {
	Name       string
	Parameters Parameters

	// Dir is where relative files of the parameters open from, such as the directory of the mock file that defines
	// the event. "" is the working directory.
	Dir string

	// Listener, when set, runs the event in place of the listener registered under Name, which then only names the
	// event in errors: it needs no registration. Its Parameters reach it as they are: [EventPreparer] doesn't apply.
	// Events set in Go can carry one, as [github.com/caffeine-projects/vlatte/mockhttp.Builder.Callback] does. Mock
	// files can't, and a mock's description leaves out the events that carry one.
	Listener EventListener[OnComplete]

	// AfterResponse has the response go out without waiting for the event, which runs on until it ends or the server
	// closes: an app that needs its callback once it has its answer, such as a webhook's, gets it. The event starts
	// as the response goes out, so it may start before the client reads it: a delay, such as a webhook's, sends it
	// later. A test waits on what the event does, such as its target getting the webhook. Its failure is reported as
	// any event's, but the journal entry of the request, recorded by then, doesn't tell it.
	AfterResponse bool
}

EventConfig describes a named event and its parameters (e.g. for mock definitions).

type EventConfigs

type EventConfigs []EventConfig

EventConfigs lists the events of one kind, such as the on_complete events of a mock.

func (EventConfigs) Has

func (e EventConfigs) Has(name string) bool

Has reports whether an event of the list has the given name.

type EventFunc

type EventFunc[T EventArgs] func(ctx context.Context, args T) error

EventFunc is a function that implements the EventListener interface.

func (EventFunc[T]) Run

func (f EventFunc[T]) Run(ctx context.Context, args T) error

Run implements the EventListener interface.

type EventListener

type EventListener[T EventArgs] interface {
	Run(ctx context.Context, args T) error
}

EventListener is an interface for handling an event with type-safe args.

type EventPreparer

type EventPreparer interface {
	Prepare(cfg EventConfig, engine TemplateEngine) (Parameters, error)
}

EventPreparer is an optional interface of event listeners. Prepare checks the parameters of an event when the mock that names the listener is built, and those of global events when the server is built, so a mistake fails the mock instead of each event. It returns the parameters Run receives, such as decoded, and can compile their templates with engine, the server's.

type EventRegistry

type EventRegistry[T EventArgs] struct {
	// contains filtered or unexported fields
}

EventRegistry is a generic registry of named listeners for one event type. Use one registry per event kind (e.g. OnComplete, OnMatch) so new events can be added without changing this type.

func NewEventRegistry

func NewEventRegistry[T EventArgs]() *EventRegistry[T]

NewEventRegistry creates a new registry for an event type.

func (*EventRegistry[T]) Has

func (r *EventRegistry[T]) Has(name string) bool

Has reports whether a listener is registered for the given name.

func (*EventRegistry[T]) Prepare

func (r *EventRegistry[T]) Prepare(cfg EventConfig, engine TemplateEngine) (Parameters, error)

Prepare returns the parameters of cfg as the listener named cfg.Name prepares them, when it is an EventPreparer. Otherwise, or when no listener has that name, it returns them as they are.

func (*EventRegistry[T]) Register

func (r *EventRegistry[T]) Register(name string, evt EventListener[T]) error

Register adds a listener under name. A name already registered keeps its listener: Register fails with ErrEventAlreadyRegistered.

func (*EventRegistry[T]) Run

func (r *EventRegistry[T]) Run(ctx context.Context, name string, args T) error

Run looks up the listener by name and runs it with the given args. It is a no-op if the name is not registered.

type EventRunner

type EventRunner struct {
	Listeners *Listener
	Global    *Events
	// contains filtered or unexported fields
}

EventRunner runs the events of mocks, such as on_complete ones, each in a goroutine of its own, and cancels them when its server closes.

func NewEventRunner

func NewEventRunner(listeners *Listener, global *Events) *EventRunner

NewEventRunner creates an EventRunner that runs the listeners registered in listeners, and the events of global before those of each mock. A nil listeners has no listener registered, and a nil global no event.

func (*EventRunner) Close

func (r *EventRunner) Close()

Close cancels the context of the events running, so the requests waiting for them end, and runs no event after. It returns once the events that run after the response have ended.

func (*EventRunner) OnComplete

func (r *EventRunner) OnComplete(ctx context.Context, mock Mock, argsFn func(ctx context.Context, cfg EventConfig) OnComplete, onErr func(err error))

OnComplete runs the on_complete events of mock, global events first, each in a goroutine of its own, and returns once they all have, but those set to run after the response: an event neither waits for nor stops another, and the request waits for the others, so a test checks what they did right after the response. See EventConfig.AfterResponse. Each listener runs with what argsFn returns and a context with the values of ctx, the request's, such as a trace: the client leaving doesn't end it, and Close does. argsFn gets it too. onErr gets the error, or the panic, of each event that fails, unless Close canceled it, worded like the errors of mocks that name a listener that isn't registered. A closed runner runs nothing.

type Events

type Events struct {
	OnComplete EventConfigs
}

Events holds the events of a mock, or the global events of a server, by kind.

type Explanation

type Explanation struct {
	Name     string
	Items    []ExplanationItem
	Children []*Explanation
	Parent   *Explanation
}

Explanation records why a mock matched a request or not, as a tree of named blocks. MatcherArg.Explain hands one to each matcher. A nil *Explanation records nothing; its methods are safe to call.

func NewExplanation

func NewExplanation() *Explanation

NewExplanation returns an empty root Explanation.

func (*Explanation) AddMessage

func (e *Explanation) AddMessage(message string)

AddMessage records a message in this block. On a nil Explanation, it does nothing.

func (*Explanation) Root

func (e *Explanation) Root() bool

Root reports whether e is the root of its tree. A nil Explanation has no parent, so it reports true.

func (*Explanation) Sub

func (e *Explanation) Sub(name string) *Explanation

Sub adds a child block with the given name and returns it. On a nil Explanation, it returns nil, which records nothing.

type ExplanationItem

type ExplanationItem struct {
	Message string
}

ExplanationItem is a message recorded in an Explanation.

type Flow

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

Flow is one stream through steps: a response body, the bytes of a response on the wire, a request body, one direction of a connection. Each step has its Stage in it, and the sink comes last.

A flow is a Stage itself: its caller calls Write, Flush and Close, one call at a time, and Close once the stream ends. Once its Flow.Context ends, Write and Flush return the cause.

func NewFlow

func NewFlow(ctx context.Context, sink Stage, steps []Step) *Flow

NewFlow opens the stages of steps in a new flow, in front of sink, from the last step to the first. The flow's context derives from ctx, typically the request's, so its stages stop when the client leaves. Steps can be shared, such as a mock's steps across its concurrent requests: NewFlow neither keeps nor changes steps.

func (*Flow) Abort

func (f *Flow) Abort(err error)

Abort aborts the flow, from any goroutine, such as a stage's timer: it ends the flow's context with a cause that matches ErrAbort and err, which may be nil. Pass ErrReset for a reset. The first end wins: once the flow ended, Abort does nothing.

func (*Flow) Close

func (f *Flow) Close() error

Close ends the stream: the stages pass on what they hold and end, in order, then the sink. Then the flow's context ends. Close returns once the last stage did, so nothing reaches the sink after it. Closing again does nothing.

func (*Flow) Context

func (f *Flow) Context() context.Context

Context returns the flow's context. It ends when the context the flow was made with does, when a stage aborts the flow, or once the flow is closed. context.Cause tells which: a cause that matches ErrAbort is an abort.

func (*Flow) Flush

func (f *Flow) Flush() error

Flush has the stages pass on what they hold, then flushes the sink.

func (*Flow) Write

func (f *Flow) Write(b []byte) (int, error)

Write passes b to the first stage. An error that matches ErrAbort or ErrReset aborts the flow.

type GetEvals

type GetEvals[M Mock, TIn any] func(m M) Evaluators[M, TIn]

GetEvals is a function that returns a list of evaluators of a given Mock. Normally Mock will have a function that returns a list of evaluators.

type GoTemplateEngine

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

GoTemplateEngine compiles Go templates, of package text/template. Servers use it when no other engine is set. It is safe for concurrent use. Create one with NewGoTemplateEngine.

text/template, and not html/template, is the package for mocks: templates write bodies of any type, such as JSON and XML, and headers, URLs and file names, which html/template would escape for HTML. Escape on purpose with the html, js and urlquery functions, or with toJSON for a JSON value.

Besides the functions of text/template, templates can call those below. A function's last argument is the value it works on, so it pipes, as in {{ .Request.Header.Get "X-Name" | trimSpace | toUpper }}, but for the list functions, which take the list first, as Go's append does. A function given a bad argument, such as an unknown algorithm or a number that isn't one, fails the template, so a mock never answers with a broken body.

  • JSON: toJSON (a value as JSON, such as a quoted and escaped string), fromJSON, JSONPath and JSONPathAll (what a JSONPath selects), deleteJSONPath and mergePatch (RFC 7386), which read a value or JSON text, and indentJSON and compactJSON.
  • Strings: toUpper, toLower, trimSpace, title, upperFirst, truncate, replaceAll, split, join, substring and slug, which count runes, not bytes; regexpFind, regexpSubmatch, regexpReplaceAll and regexpMatch, with Go's regexp syntax; contains, for a substring, a list item or a map key; default, for a value that is nil, "" or empty.
  • Numbers: add, sub, mul, div and mod, on ints when both operands are integers and on float64s otherwise, and toInt and toFloat, which convert numbers and numeric strings, such as a query parameter.
  • Lists: list, append, insert, remove, randomItem, shuffle, reverse and sortBy, which return new lists.
  • XML: xpath, the text of what an XPath 1.0 expression selects, and xpathNodes, the nodes it selects.
  • Time: now, the current time.Time; formatTime and parseTime, with Go layouts, the names of those of package time, such as RFC3339, and unix and unixMilli; timeAdd, by a Go duration or such as "3 days"; inZone; and truncateTime, such as to "first day of month".
  • Encoding: base64Encode and base64Decode, queryUnescape and parseQuery.
  • Random: uuid, randomString, randomInt and randomFloat. They are not for secrets.
  • Hashes: hash, such as {{ hash "sha256" "hex" .Request.Body.String }}, and hmac.
  • env: the value of an environment variable WithEnv allows. A name env is given as a literal and isn't allowed fails the template when it compiles.

A missing map key is an error, so a typo doesn't render as "<no value>". Read an optional key with index, which gives the zero value, as in {{ with index .Extra "name" }}{{ . }}{{ end }}, or change the rule with WithMissingKey.

func MustNewGoTemplateEngine

func MustNewGoTemplateEngine(opts ...GoTemplateOption) *GoTemplateEngine

MustNewGoTemplateEngine is like NewGoTemplateEngine but panics if an option fails.

func NewGoTemplateEngine

func NewGoTemplateEngine(opts ...GoTemplateOption) (*GoTemplateEngine, error)

NewGoTemplateEngine creates a GoTemplateEngine.

func (*GoTemplateEngine) Compile

func (e *GoTemplateEngine) Compile(name, text string) (Template, error)

Compile parses text as a Go template. The error is text/template's, such as "template: body:1: unclosed action", or names an environment variable env is given and the engine doesn't allow.

type GoTemplateOption

type GoTemplateOption func(*GoTemplateEngine) error

GoTemplateOption configures a GoTemplateEngine.

func WithEnv

func WithEnv(names ...string) GoTemplateOption

WithEnv allows templates to read the environment variables names with env. Names are exact and case-sensitive, and each call adds to the names allowed. Without it, templates read none: env fails for every name.

func WithMissingKey

func WithMissingKey(mode string) GoTemplateOption

WithMissingKey sets what a template does with a missing map key, as the missingkey option of text/template: "error" stops it with an error, the default; "default" and "invalid" print "<no value>"; "zero" prints the zero value of the map's element type, which is "<no value>" too for a map[string]any.

func WithTemplateFuncs

func WithTemplateFuncs(funcs map[string]any) GoTemplateOption

WithTemplateFuncs adds functions templates can call, by name. A function replaces a built-in one of the same name. Each must be a function text/template can call: one result, or two with an error second.

type Inspector

type Inspector[M Mock] interface {
	Hits() int64
	ListPending() []M
	ListCalled() []M
}

Inspector reports the hits and the calls of a group of mocks, such as a Scope or a whole server, for Times and NotCalled.

type KV

type KV interface {
	// Get returns the parameter by its key.
	Get(ctx context.Context, k string) ([]byte, error)

	// Has checks if a parameter with the given key exists.
	Has(ctx context.Context, k string) (bool, error)

	// Set sets a parameter.
	Set(ctx context.Context, k string, v []byte) error

	// Del removes a parameter by its key.
	Del(ctx context.Context, k string) error
}

KV defines a contract for a generic parameters repository.

func NewMemoryKV

func NewMemoryKV() KV

NewMemoryKV returns a KV that keeps the parameters in memory.

type Kind

type Kind string

Kind names the protocol of a mock server, such as "http". Each protocol package declares its own, for example mockhttp.Kind, so adding a protocol doesn't change this package. Protocol-neutral components, such as event listeners (Listener, OnComplete), can use it to tell their inputs apart.

func (Kind) String

func (k Kind) String() string

String implements fmt.Stringer.

type Kit

type Kit interface {
	// TemplateEngine is the engine of the server, so a component's templates call the same functions as the
	// templates of replies.
	TemplateEngine() TemplateEngine

	// Data is the template data of the request being served.
	Data() any

	// Logger is the server's logger, never nil, so a component logs where the server does, and as little: a server
	// that is silent gives one that logs nothing.
	Logger() *zerolog.Logger

	// Transport is what the server sends HTTP requests through, never nil, so a component's requests, such as
	// webhooks, share its settings and connections, and their connections don't outlive the server.
	Transport() http.RoundTripper

	// Dir is where relative files open from: the directory of the mock file that defines what is served, such as an
	// event, or "" for the working directory, as for what is defined in Go.
	Dir() string
}

Kit is what the mock pipeline gives its components, such as event listeners: the server's dependencies and services, and what they serve, never its settings.

type Listener

type Listener struct {
	OnComplete *EventRegistry[OnComplete]
}

Listener holds registries for each event kind. Add new event types by defining an interface (embedding EventArgs), adding a field here, and initializing it in NewListener.

func NewListener

func NewListener() *Listener

NewListener creates a Listener with all event registries initialized.

type LogLevel

type LogLevel = int8

LogLevel sets the level of the default logger.

type LogVerbosity

type LogVerbosity int8

LogVerbosity defines the verbosity of the logs.

const (
	// LogBasic logs the requests and their answers without their headers or bodies.
	LogBasic LogVerbosity = iota
	// LogHeader adds the headers, with those the server redacts redacted.
	LogHeader
	// LogBody adds the bodies.
	LogBody
)

Log verbosities, each logging what the one before it does, and more.

func (LogVerbosity) String

func (l LogVerbosity) String() string

String implements fmt.Stringer. It returns "none" for a value that is not a verbosity.

type Matcher

type Matcher interface {
	Name() string
	Match(arg MatcherArg) (bool, error)
}

Matcher matches values.

func MatcherFunc

func MatcherFunc(name string, fn func(arg MatcherArg) (bool, error)) Matcher

MatcherFunc returns a Matcher named name that matches with fn.

type MatcherArg

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

MatcherArg is the argument a Matcher gets: the value to match, with the context and the explanation of the match.

func NewMatcherArg

func NewMatcherArg(ctx context.Context, exp *Explanation, v any) MatcherArg

NewMatcherArg creates a MatcherArg for v, such as for a matcher that hands part of its value to another. exp, which records why the value matched or not, can be nil.

func (*MatcherArg) Context

func (a *MatcherArg) Context() context.Context

Context returns the context of the match, such as that of the request.

func (*MatcherArg) Explain

func (a *MatcherArg) Explain() *Explanation

Explain returns the optional explain block for recording Matcher messages. E.g. why a Matcher did not match).

func (*MatcherArg) Mock

func (a *MatcherArg) Mock() Mock

Mock returns the mock whose turn a Claimer takes, in the argument of its Claim. It is nil in the argument of Match, which has no turn to take.

func (*MatcherArg) Value

func (a *MatcherArg) Value() any

Value returns the value to match, such as the part of the request the Selector of an Eval selected.

type MemoryStore

type MemoryStore[B Mock] struct {
	// contains filtered or unexported fields
}

MemoryStore is a Store that keeps its mocks in memory.

func (*MemoryStore[B]) Clear

func (s *MemoryStore[B]) Clear(_ context.Context) error

Clear removes every mock, and closes them once the store is unlocked.

func (*MemoryStore[B]) Delete

func (s *MemoryStore[B]) Delete(_ context.Context, ids ...string) error

Delete removes the mocks with the given IDs, skipping those it doesn't hold, and closes the removed mocks that implement io.Closer once the store is unlocked. It never fails.

func (*MemoryStore[B]) Health

func (s *MemoryStore[B]) Health(context.Context) error

Health returns nil: a memory store is always ready.

func (*MemoryStore[B]) List

func (s *MemoryStore[B]) List(ctx context.Context) (iter.Seq[B], error)

List returns the enabled mocks, in the order they match, as they were when List was called. It never fails.

func (*MemoryStore[B]) Pick

func (s *MemoryStore[B]) Pick(_ context.Context, id string) (B, error)

Pick returns the mock with the given ID, enabled or not. It fails with ErrNotFound when no mock has it.

func (*MemoryStore[B]) PickAll

func (s *MemoryStore[B]) PickAll(_ context.Context) ([]B, error)

PickAll returns every mock, enabled or not, in the order they match. It never fails.

func (*MemoryStore[B]) Replace

func (s *MemoryStore[B]) Replace(_ context.Context, old []B, mocks ...B) error

Replace removes the mocks of old it holds, the very instances, and stores mocks, in one step. A mock of old that it stores again keeps its place, so it matches in the same order. It changes nothing when an ID of mocks is held by a mock it keeps, or repeated in the call.

func (*MemoryStore[B]) Store

func (s *MemoryStore[B]) Store(_ context.Context, mocks ...B) error

Store adds mocks, ordered by priority and then by when they were stored. It stores none of them when an ID is already stored or repeated in the call.

type Misses

type Misses[M Mock, TIn any] struct {
	// contains filtered or unexported fields
}

Misses are the mocks Mocks.Find tried for a request, and that didn't match it, each with the part it stopped at: none once a mock matched. The zero value is ready to use. A server may reuse one, a request at a time, such as through a sync.Pool: Find starts it over, and Reset lets go of what it kept.

func (*Misses[M, TIn]) NearMisses

func (m *Misses[M, TIn]) NearMisses(ctx context.Context, describe func(*Eval[TIn]) string) []NearMiss

NearMisses returns the mocks that came closest to matching the request Find found no mock for: at most 3, those that differ in the fewest parts of the request first, then those with the most parts that passed, in the order Find tried them. A part that keeps state, such as a Times used up, is no part of the request: a mock that missed by it alone comes first. describe names the part of the request an evaluator checks, such as "header Accept"; a part whose matcher failed with an error names it too.

No matcher runs twice for a request: each mock goes on from the part Find stopped at, which failed, and only the parts after it run now. Of those, it skips the Stateful ones, as running them may change their state, and runs the Claimer ones, whose Match changes nothing, such as a scenario's. A mock none of whose request parts passed is left out, unless it differs in none.

The report is whole even once ctx, the request's, is done, as when its client left: the matchers get a context with its values that isn't.

func (*Misses[M, TIn]) Reset

func (m *Misses[M, TIn]) Reset()

Reset lets go of the mocks and the request Find kept, keeping the room they took for the next request.

type Mock

type Mock interface {
	ID() string
	Name() string
	Priority() int
	Hits() int64
	Called() bool
	Enabled() bool
	Enable()
	Disable()
	Events() Events
	String() string
}

Mock is what the core needs from a protocol's mock: identity, ordering, state, hits and events. Protocol packages add what only they use, such as callbacks or steps, on their own mock types.

A mock that holds something to release, such as the connections of an HTTP client, implements io.Closer: the store closes it when it leaves, and a builder's mock that never makes it into the store is closed too.

A store tells mocks apart by identity, as when it replaces the very mocks it was given: a mock is a pointer, or another type Go can compare, never a struct holding a slice, a map or a func.

type MockBuilder

type MockBuilder[B Mock] interface {
	BuildFromBytes(content []byte, extension string) (B, error)
}

MockBuilder knows how to turn a raw mock definition (content + extension) into a concrete Mock for a specific mock server implementation.

It exists so generic tooling (such as the admin API) can stay transport-agnostic: each mock server (HTTP, gRPC, ...) plugs in its own builder that understands its own mock format.

type MockBuilderFunc

type MockBuilderFunc[B Mock] func(content []byte, extension string) (B, error)

MockBuilderFunc adapts a plain function to the MockBuilder interface.

func (MockBuilderFunc[B]) BuildFromBytes

func (f MockBuilderFunc[B]) BuildFromBytes(content []byte, extension string) (B, error)

BuildFromBytes implements MockBuilder.

type Mocks

type Mocks[M Mock, TIn any] iter.Seq[M]

Mocks is a list of sorted eligible mocks. This list can searched for a match.

func (Mocks[M, TIn]) Find

func (mocks Mocks[M, TIn]) Find(
	ctx context.Context,
	sub *Explanation,
	in TIn,
	evals GetEvals[M, TIn],
	misses *Misses[M, TIn],
) (M, bool, error)

Find tries to find an exact match in the mocks. It returns the mock that matched and a boolean indicating if a match was found. misses, when not nil, starts over and keeps each mock that didn't match, with the part it stopped at, which Misses.NearMisses goes on from.

The mock found has taken the turn of its Claimer matcher. When another request took it first, Find looks again from the first mock, as the mocks now match in the state that request left. A retry adds to sub. Find stops with the error of ctx once it ends.

type NearMiss

type NearMiss struct {
	MockID  string   `json:"mock_id"`
	Name    string   `json:"name,omitempty"`
	Passed  int      `json:"passed"`
	Total   int      `json:"total"`
	Failed  []string `json:"failed,omitempty"`
	Skipped []string `json:"skipped,omitempty"`
}

NearMiss is a mock that came close to matching a request: Passed of the Total parts it checked matched. Failed names the parts that didn't, and Skipped those it didn't check, which keep state, such as a scenario's, that matching never reached.

type OnComplete

type OnComplete interface {
	EventArgs

	Mock() Mock
}

OnComplete is the event of a mock that replied. Its listeners run each in a goroutine of its own, and the response ends once they all have: see EventRunner.

type Parameters

type Parameters any

Parameters holds the parameters of an event, as a mock file decodes them or Go code sets them. What they hold is up to the listener of the event.

type Recipe

type Recipe[B Mock, A Server[B, A]] interface {
	Prep(ctx context.Context, input *RecipeInput[B, A]) error
}

Recipe prepares a mock server, such as by adding mocks to it. The Prep method of a server runs recipes in order, and stops at the first that fails.

type RecipeFunc

type RecipeFunc[B Mock, A Server[B, A]] func(ctx context.Context, input *RecipeInput[B, A]) error

RecipeFunc is a function that implements Recipe.

func (RecipeFunc[B, A]) Prep

func (f RecipeFunc[B, A]) Prep(ctx context.Context, input *RecipeInput[B, A]) error

Prep implements Recipe.

type RecipeInput

type RecipeInput[B Mock, A Server[B, A]] struct {
	Server Server[B, A]
}

RecipeInput is what a Recipe prepares: Server is the mock server.

type RecordBody

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

RecordBody captures a body as it is sent. It keeps it in memory up to MaxInlineBody. Past that, a goroutine of its own writes it to a file of the recording's directory, so the size of the body never holds memory. A sender faster than the disk waits for it, a second in all at most: a body the disk falls further behind, or larger than MaxBodySize, isn't kept. Its call isn't recorded, and Err says why.

func (*RecordBody) Bytes

func (b *RecordBody) Bytes() ([]byte, bool)

Bytes returns the body once closed, when it stayed in memory. It returns false when it went to a file, or wasn't kept.

func (*RecordBody) Close

func (b *RecordBody) Close()

Close ends the body: it keeps nothing more.

func (*RecordBody) Discard

func (b *RecordBody) Discard()

Discard drops the body, and the file it went to: its call won't be recorded. A placed body is the recording's.

func (*RecordBody) Err

func (b *RecordBody) Err() error

Err returns why the body wasn't kept, or nil.

func (*RecordBody) Keep

func (b *RecordBody) Keep(p []byte)

Keep keeps p, the next bytes of the body. It waits for the disk only when the body goes to its file faster than the disk takes it, as the type says. It does nothing once the body is closed or dropped. p is the caller's again when Keep returns.

func (*RecordBody) Len

func (b *RecordBody) Len() int64

Len returns how many bytes the body had.

type RecordCodec

type RecordCodec[X, Q, S any] interface {
	// Name names the mock of x for people, such as "GET /items?page=2". Its file names start with it.
	Name(x X) string

	// Request returns what a playback of x matches. files places the bodies it holds.
	Request(x X, files *RecordFiles) (Q, error)

	// Response returns what x answered. files places the bodies it holds.
	Response(x X, files *RecordFiles) (S, error)

	// Mock returns the mock file of m, which has one response at least: a value written as JSON, then as the format of
	// the recording.
	Mock(m *RecordedMock[Q, S]) any
}

RecordCodec is what a protocol tells a RecordSession: how a call it captured, X, reads as a mock. Q is the request section of the mock, what it matches, and S a response section, what it replies with. Both are written as JSON with encoding/json/v2, such as structs with json tags. Calls whose Q writes the same JSON are the same request: they play back as one mock, so Q must write equivalent requests the same way.

type RecordFiles

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

RecordFiles places the bodies of one call: inline in its mock file, or in files next to it, written with the mock. Files are named after their content, so equal bodies share one.

func (*RecordFiles) Place

func (f *RecordFiles) Place(body []byte, text bool, ext string) (name string, inline bool)

Place returns where body goes. It goes inline when text is true and the recording neither writes every body to a file nor finds body longer than MaxInlineBody; otherwise to a file next to the mock file, whose name it returns, and which ext ends, such as ".json". Place keeps body until the mock is written: the caller doesn't change it after.

func (*RecordFiles) PlaceSpooled

func (f *RecordFiles) PlaceSpooled(b *RecordBody, ext string) (string, error)

PlaceSpooled places a body that went to a file while it was captured: the file moves next to the mock file, under the name it returns, which ext ends. It waits for the file to be written. It fails when the body wasn't kept, or stayed in memory: Place takes its Bytes.

type RecordOptions

type RecordOptions[Q, S any] struct {
	// Dir is where the mock files and body files go. It is created when missing.
	Dir string

	// Format is the format of the mock files: json, yaml or yml.
	Format string

	// Repeats is what the recording keeps of a request that comes again. 0 means RecordSequence.
	Repeats RecordRepeats

	// BodyFiles writes every body to a file of its own, instead of inline in the mock file.
	BodyFiles bool

	// MaxInlineBody is how much of a body stays in memory; a larger body goes to a file while it is captured. A text
	// body that stays in memory is written inline in the mock file. 0 means 1 MiB.
	MaxInlineBody int64

	// MaxBodySize bounds the bodies recorded: a call with a larger one isn't recorded. 0 means 1 GiB; a negative
	// value means no limit.
	MaxBodySize int64

	// Transforms change each mock before it is written, in order. A transform that fails skips the mock, and so do
	// transforms that leave it no response.
	Transforms []func(*RecordedMock[Q, S]) error

	Logger *zerolog.Logger
}

RecordOptions configures a RecordSession.

type RecordRepeats

type RecordRepeats int

RecordRepeats is what a recording keeps of the calls that repeat a request.

const (
	// RecordSequence keeps every response, in the order they came: the mock replies with them in turn, then with the
	// last one again. When every response is the same, it keeps one. It is the default, which zero means.
	RecordSequence RecordRepeats = iota + 1

	// RecordFirst keeps the first response only.
	RecordFirst
)

type RecordResult

type RecordResult struct {
	Recordings []Recording
	Status     RecordStatus
}

RecordResult is what a recording wrote, in the order its requests first came.

type RecordSession

type RecordSession[X, Q, S any] struct {
	// contains filtered or unexported fields
}

RecordSession records the calls of a protocol as mock files, off the request path. Record queues a call and returns at once; a goroutine of the session turns calls into mocks and writes them. Calls of one request make one mock.

func MustNewRecordSession

func MustNewRecordSession[X, Q, S any](c RecordCodec[X, Q, S], o RecordOptions[Q, S]) *RecordSession[X, Q, S]

MustNewRecordSession is NewRecordSession, and panics when it fails.

func NewRecordSession

func NewRecordSession[X, Q, S any](c RecordCodec[X, Q, S], o RecordOptions[Q, S]) (*RecordSession[X, Q, S], error)

NewRecordSession starts a recording into o.Dir, which it creates when missing. It removes the files a recording that ended unexpectedly left half written there: one recording writes to a directory at a time.

func (*RecordSession[X, Q, S]) Done

func (s *RecordSession[X, Q, S]) Done() <-chan struct{}

Done closes once the recording stopped and wrote everything it took.

func (*RecordSession[X, Q, S]) NewBody

func (s *RecordSession[X, Q, S]) NewBody() *RecordBody

NewBody returns a body to capture into, for a call of this session.

func (*RecordSession[X, Q, S]) Record

func (s *RecordSession[X, Q, S]) Record(x X, size int) bool

Record queues x, a call to record, and returns at once. size is what x holds in memory. It returns false when the recording stopped, or when the calls waiting to be written hold too much already. A call the session doesn't record is discarded: when X has a Discard method, the session calls it.

func (*RecordSession[X, Q, S]) Status

func (s *RecordSession[X, Q, S]) Status() RecordStatus

Status counts the calls of the recording.

func (*RecordSession[X, Q, S]) Stop

func (s *RecordSession[X, Q, S]) Stop(ctx context.Context) (*RecordResult, error)

Stop stops taking calls, writes those it took, and returns what it wrote. Calls still being captured aren't recorded. It waits for the writing, or for ctx: when ctx ends first, it returns ctx's error, and a later Stop waits again. Done closes once the writing ends. The error joins the calls and mocks that failed.

type RecordStatus

type RecordStatus struct {
	// Active is true while the recording takes calls.
	Active bool

	// Calls is how many calls it took.
	Calls int64

	// Mocks is how many mocks it wrote.
	Mocks int64

	// Dropped is how many calls it refused, because too many waited to be written.
	Dropped int64

	// Failed is how many calls and mocks it couldn't record.
	Failed int64
}

RecordStatus counts the calls of a recording.

type RecordedMock

type RecordedMock[Q, S any] struct {
	// ID names the mock and its file. A transform can't change it.
	ID        string
	Name      string
	Request   Q
	Responses []S
}

RecordedMock is a mock a recording made: a request, and the responses recorded for it, in the order they came.

type Recording

type Recording struct {
	ID   string
	Name string

	// File is the path of the mock file.
	File string

	// BodyFiles are the names of its body files, next to the mock file.
	BodyFiles []string

	// Responses is how many responses it replies with.
	Responses int
}

Recording is a mock a recording wrote.

type Replier

type Replier[I any, R any] interface {
	Reply(ctx context.Context, input I) (R, error)
}

Replier makes the reply of a mock to input, which holds the request the mock matched. A ReplierFactory makes one for each mock.

type ReplierFactory

type ReplierFactory[I any, R any, A any] interface {
	NewReplier(app A) (Replier[I, R], error)
}

ReplierFactory makes the Replier of a mock. A mock builder calls NewReplier once for each mock it builds, with the server A it builds the mock for, so:

  • each mock gets a Replier of its own: what it keeps, such as compiled templates or a position in a sequence, belongs to that mock;
  • one factory can serve many mocks and servers, and changing it later changes only the mocks built afterwards.

NewReplier must not change the factory: mocks for different servers can be built at the same time. The Replier must not share a map or slice that the methods of the factory change. A factory that holds other factories calls their NewReplier.

type Scope

type Scope[B Mock] struct {
	// contains filtered or unexported fields
}

Scope keeps a reference to a group of mocks.

func NewScope

func NewScope[B Mock](mocks []B, store Store[B]) *Scope[B]

NewScope creates a Scope that holds references to the given mocks and store. With a Scope instance, it is possible to verify if one or a group of mocks were called, how many times they were called and so on.

func (*Scope[B]) AssertCalled

func (s *Scope[B]) AssertCalled(t TestingT) bool

AssertCalled reports an error if there are still pending Mock instances.

func (*Scope[B]) AssertNotCalled

func (s *Scope[B]) AssertNotCalled(t TestingT) bool

AssertNotCalled reports an error if any mock was called.

func (*Scope[B]) AssertNumberOfCalls

func (s *Scope[B]) AssertNumberOfCalls(t TestingT, expected int64) bool

AssertNumberOfCalls asserts that the sum of matched request hits is equal to the given expected value.

func (*Scope[B]) Called

func (s *Scope[B]) Called() bool

Called returns true if all Scope Mock instances were called at least once.

func (*Scope[B]) Clear

func (s *Scope[B]) Clear(ctx context.Context) error

Clear removes all scoped Mock instances.

func (*Scope[B]) Delete

func (s *Scope[B]) Delete(ctx context.Context, ids ...string) error

Delete removes the mocks of this scope with the given IDs from the store. IDs of other mocks are ignored. A mock that left the store some other way stays out, even when its ID now names another mock: mocks are told apart by identity, so Mock implementations must be comparable, as pointers are.

func (*Scope[B]) Disable

func (s *Scope[B]) Disable()

Disable disables all Mock instances kept in this Scope. Disabled Mocks will be ignored during matching.

func (*Scope[B]) Enable

func (s *Scope[B]) Enable()

Enable enables all Mock instances kept in this Scope.

func (*Scope[B]) Exists

func (s *Scope[B]) Exists(id string) bool

Exists reports whether this Scope keeps a mock with the given ID.

func (*Scope[B]) Hits

func (s *Scope[B]) Hits() int64

Hits returns the sum of all matched request hits across the Scope.

func (*Scope[B]) IDs

func (s *Scope[B]) IDs() []string

IDs returns the IDs of the mocks kept in this Scope, in no particular order.

func (*Scope[B]) ListCalled

func (s *Scope[B]) ListCalled() []B

ListCalled returns all Mock instances that were called.

func (*Scope[B]) ListPending

func (s *Scope[B]) ListPending() []B

ListPending returns all Mock instances that were not called at least once.

func (*Scope[B]) Mock

func (s *Scope[B]) Mock(id string) B

Mock returns the Mock with the given ID, or the zero value if not found.

func (*Scope[B]) Mocks

func (s *Scope[B]) Mocks() []B

Mocks returns all Mock instances kept in this Scope.

func (*Scope[B]) Pending

func (s *Scope[B]) Pending() bool

Pending returns true when there are one or more Mock instances that were not called at least once.

type Scopes

type Scopes[B Mock, A any] struct {
	// contains filtered or unexported fields
}

Scopes is a collection of Scope instances with coordinated add and retrieval.

func (*Scopes[B, A]) Add

func (s *Scopes[B, A]) Add(builders ...Builder[B, A]) (*Scope[B], error)

Add builds every builder, then stores the mocks in one call and returns their scope. It stores nothing when a builder fails, and reports every failing builder by index, or when the store refuses the mocks, for example over an ID already in use.

func (*Scopes[B, A]) All

func (s *Scopes[B, A]) All() []*Scope[B]

All returns the scopes tracked, in the order they were added. A scope that replaced another takes its place.

func (*Scopes[B, A]) Remove

func (s *Scopes[B, A]) Remove(ctx context.Context, scope *Scope[B]) error

Remove deletes the mocks of scope and stops tracking it.

func (*Scopes[B, A]) Replace

func (s *Scopes[B, A]) Replace(ctx context.Context, old *Scope[B], builders ...Builder[B, A]) (*Scope[B], error)

Replace builds every builder, then swaps the mocks for those of old in one step, so a reader finds the mocks of old or the new ones, never neither, and returns their scope, which takes the place of old. With a nil old, it adds the mocks, as Add does. A mock stored outside every scope, such as one the admin API added, gives way in the same step to a new mock with its ID. It changes nothing when old isn't tracked, when a builder fails, reporting every failing builder by index, or when the store refuses the mocks, for example over an ID in use in another scope. Once replaced, old holds no mocks.

type Selector

type Selector[TIn any] func(ctx context.Context, input TIn) any

Selector selects a value from the parameter input. The return value could be the input itself or a specific field of it.

type Server

type Server[B Mock, A any] interface {
	Hits() int64
	Enable()
	Disable()
	Clean(ctx context.Context) error
	Mock(builders ...Builder[B, A]) (*Scope[B], error)
}

Server is the interface every mock server implements. It covers both mock registration and lifecycle management.

type Sink

type Sink struct{ W io.Writer }

Sink is a Stage that ends a flow: it writes what it gets to W.

func (*Sink) Close

func (s *Sink) Close() error

Close does nothing: W is its owner's to close.

func (*Sink) Flush

func (s *Sink) Flush() error

Flush flushes W when it can, with a Flush() error method, such as a bufio.Writer has.

func (*Sink) Write

func (s *Sink) Write(p []byte) (n int, err error)

Write writes p to W and returns what W returns, so a Sink is also an io.Writer.

type Stage

type Stage interface {
	Write(b []byte) (n int, err error)
	Flush() error
	Close() error
}

Stage is a step's part of one Flow. Write follows io.Writer: n is how much of b the stage took, which is len(b) when it passed b on changed, in parts, later, or not at all.

A stage decides what goes on to next: b as it is, changed or in parts, later, or nothing. It fails the flow by returning an error. One that matches ErrAbort or ErrReset aborts it. Flush passes on what the stage holds, then flushes next. Close passes on what it holds, then closes next.

The flow's caller calls a stage one call at a time. A stage may call next from a goroutine of its own, as one that delays its writes does: its calls never overlap, and all of them end before its Close returns. b is the caller's, as for io.Writer: a stage must not change its bytes, and copies it to keep it once Write returned. The flow clips every b, so append always copies it. Once the flow's Flow.Context ends, a stage drops what it holds, returns from Close at once, and its goroutines and timers end: what it passes on from then on doesn't reach the sink.

type Stateful

type Stateful interface {
	SetState() error
}

Stateful describes a matcher that has post processes that need to be executed after the matched mock is served. Dedicated for stateful matchers.

type Step

type Step interface {
	Open(flow *Flow, next Stage) Stage
}

Step makes its Stage of each Flow it is part of, in front of next: the rest of the flow. A step keeps what lasts the whole flow in its stage, such as a count of the bytes it passed. A mock's steps make the stages of all its responses: Open must be safe for concurrent use.

flow gives the stage the flow's context, and a way to abort it from a timer: see Flow.Abort.

type StepFunc

type StepFunc func(ctx context.Context, b []byte, next Stage) (n int, err error)

StepFunc is a Step with no state, an adapter for ordinary functions: its stage calls it for each write, with the flow's context, and passes Flush and Close on.

Example:

StepFunc(func(ctx context.Context, b []byte, next Stage) (int, error) {
    if _, err := next.Write(bytes.ToUpper(b)); err != nil {
        return 0, err
    }

    return len(b), nil
})

func (StepFunc) Open

func (f StepFunc) Open(flow *Flow, next Stage) Stage

Open makes the stage of f in flow, in front of next.

type Store

type Store[T Mock] interface {
	List(ctx context.Context) (iter.Seq[T], error)

	// Store adds mocks. It fails with ErrDuplicateID, storing none of them,
	// when an ID is already stored or repeated in the call.
	Store(ctx context.Context, mocks ...T) error

	// Replace removes the mocks of old it holds and stores mocks, in one step: a reader finds the mocks of old or
	// the new ones, never neither. Mocks are told apart by identity, so a mock of old that left the store stays out,
	// even when its ID now names another mock; Mock implementations must be comparable, as pointers are.
	// It fails with ErrDuplicateID, changing nothing, when an ID of mocks is held by a mock it keeps,
	// or repeated in the call.
	Replace(ctx context.Context, old []T, mocks ...T) error

	Pick(ctx context.Context, id string) (T, error)
	PickAll(ctx context.Context) ([]T, error)

	Delete(ctx context.Context, ids ...string) error
	Clear(ctx context.Context) error

	// Health returns an error when the store can't serve its mocks, such as when the database behind it is down. The
	// admin API answers GET /readyz with it.
	Health(ctx context.Context) error
}

Store holds the mocks of a server. Replace, Delete and Clear close the mocks they remove that implement io.Closer, once the store is unlocked; a mock Replace stores again stays open, and keeps its place in the order mocks match.

func NewStore

func NewStore[B Mock]() Store[B]

NewStore creates an empty MemoryStore.

type Swap

type Swap[TM Mock] func(built, gone []TM) (drop, add []TM, err error)

Swap gives Base.RemockWith more mocks to swap in the same step. built are the mocks of the builders, and gone the stored mocks they take the place of: those of the old scope, and those outside every scope whose IDs built take. Swap returns the stored mocks to remove too, and the mocks to add too. The new scope doesn't hold those: they stay the caller's, to close when RemockWith fails.

type Template

type Template interface {
	// Execute writes the template applied to data to w. A mock executes its templates for every request it serves,
	// so Execute must be safe for concurrent use.
	Execute(w io.Writer, data any) error
}

Template is a compiled template.

type TemplateDataProvider

type TemplateDataProvider[In any] interface {
	Key() string
	Data(in In) (any, error)
}

TemplateDataProvider adds data to the templates of a server's mocks, which read it as .Provided.<Key>. In is what the server gives it for each request it serves, such as a mockhttp.TemplateDataProviderInput.

type TemplateEngine

type TemplateEngine interface {
	// Compile parses text. name says which template it is in errors, such as "body" or "header.X-Id".
	Compile(name, text string) (Template, error)
}

TemplateEngine compiles templates: the bodies, headers and body file names of replies, the parts of multipart bodies, and the parameters of events, such as webhooks. A server compiles all of them with one engine.

Templates compile when a mock is built, so one that doesn't compile fails the mock rather than a request. A body file whose content is a template compiles when a request reads it, since its name can depend on the request, so Compile must be safe for concurrent use.

GoTemplateEngine is the engine vlatte ships, and the default. Another engine, such as one for Handlebars, plugs in by implementing this interface.

type TestingT

type TestingT interface {
	Helper()
	Logf(format string, a ...any)
	Error(a ...any)
	Errorf(format string, a ...any)
}

TestingT is based on testing.T and is used for assertions. See Assert* methods on the application instance.

Directories

Path Synopsis
Package admin is the admin API of a mock server: a RESTful HTTP server of its own, on its own address, that lists, adds and changes the server's mocks, and reads its journal, while it runs.
Package admin is the admin API of a mock server: a RESTful HTTP server of its own, on its own address, that lists, adds and changes the server's mocks, and reads its journal, while it runs.
cmd
vlatte command
Command vlatte runs a mock server: it serves the mock files under testdata/vlatte/_mocks in the working directory, and an admin API on an address of its own.
Command vlatte runs a mock server: it serves the mock files under testdata/vlatte/_mocks in the working directory, and an admin API on an address of its own.
Package descriptor provides a way to describe a type, allowing it to be properly marshalled.
Package descriptor provides a way to describe a type, allowing it to be properly marshalled.
Package fault provides steps that make a mock answer wrong, to test how a client handles a server that fails: no answer at all, a malformed one, garbage, a reset connection, a body that trickles in, a slow or distant network.
Package fault provides steps that make a mock answer wrong, to test how a client handles a server that fails: no answer at all, a malformed one, garbage, a reset connection, a body that trickles in, a slow or distant network.
faultbuild
Package faultbuild builds the faults of package github.com/caffeine-projects/vlatte/fault from the fault specs of mock files: an object whose type names the fault and whose other keys are its parameters, such as {"type": "latency", "latency": "300ms", "jitter": "60ms"}.
Package faultbuild builds the faults of package github.com/caffeine-projects/vlatte/fault from the fault specs of mock files: an object whose type names the fault and whose other keys are its parameters, such as {"type": "latency", "latency": "300ms", "jitter": "60ms"}.
internal
admin
Package admin serves the admin API of a mock server: a RESTful HTTP server of its own, on its own address, the same for mock servers of every protocol.
Package admin serves the admin API of a mock server: a RESTful HTTP server of its own, on its own address, the same for mock servers of every protocol.
admin/handler
Package handler holds the admin operations of a mock server, and the units of the admin API that serve them over HTTP.
Package handler holds the admin operations of a mock server, and the units of the admin API that serve them over HTTP.
adminapi
Package adminapi is what a mock server and its admin API share: what the server gives the admin API, its Capabilities, and the API that serves them.
Package adminapi is what a mock server and its admin API share: what the server gives the admin API, its Capabilities, and the API that serves them.
atomicfile
Package atomicfile writes files that readers find whole, or not at all.
Package atomicfile writes files that readers find whole, or not at all.
bodyfile
Package bodyfile opens the files bodies are read from, such as a reply's body_file.
Package bodyfile opens the files bodies are read from, such as a reply's body_file.
config
Package config loads settings from files, environment variables and command-line arguments into a configuration struct, following a schema that names, types and documents every setting.
Package config loads settings from files, environment variables and command-line arguments into a configuration struct, following a schema that names, types and documents every setting.
httpx
Package httpx holds the small HTTP helpers the mock servers and the webhook package share.
Package httpx holds the small HTTP helpers the mock servers and the webhook package share.
mockid
Package mockid checks the IDs of mocks, which name them in admin API paths, such as /mocks/{id}.
Package mockid checks the IDs of mocks, which name them in admin API paths, such as /mocks/{id}.
mpart
Package mpart writes multipart bodies, for replies and webhooks, with mime/multipart.Writer: their model, the checks of the model, and the tables that decode it from mock files.
Package mpart writes multipart bodies, for replies and webhooks, with mime/multipart.Writer: their model, the checks of the model, and the tables that decode it from mock files.
params
Package params gives templates the parameters of a mock server, as text.
Package params gives templates the parameters of a mock server, as text.
tmplfunc
Package tmplfunc holds the functions of vlatte's Go template engine.
Package tmplfunc holds the functions of vlatte's Go template engine.
value
Package value is the data model that documents, settings and matchers share: how JSON is read, how text reads as a bool or a number, and how values compare.
Package value is the data model that documents, settings and matchers share: how JSON is read, how text reads as a bool or a number, and how values compare.
xmldoc
Package xmldoc reads XML documents into trees that keep them as written, and navigates them for XPath.
Package xmldoc reads XML documents into trees that keep them as written, and navigates them for XPath.
Package journal keeps the requests a mock server serves, of any protocol, whatever their outcome, so a test can verify after the fact what the code under test sent: a changelog of JSON lines in segment files, with an index in memory that narrows queries.
Package journal keeps the requests a mock server serves, of any protocol, whatever their outcome, so a test can verify after the fact what the code under test sent: a changelog of JSON lines in segment files, with an index in memory that narrows queries.
Package match provides request matchers, such as Equal, JSONPath, All and Not.
Package match provides request matchers, such as Equal, JSONPath, All and Not.
internal/timetext
Package timetext reads dates and times as the date and time matchers write them.
Package timetext reads dates and times as the date and time matchers write them.
matchbuild
Package matchbuild builds matchers from the matcher specs of mock files: a list whose first item names the matcher and whose other items are its arguments, such as ["contains", "x"] or ["jsonpath", "$.a", ["gt", 1]].
Package matchbuild builds matchers from the matcher specs of mock files: a list whose first item names the matcher and whose other items are its arguments, such as ["contains", "x"] or ["jsonpath", "$.a", ["gt", 1]].
matchrepeat
Package matchrepeat limits how many times a mock is served: Repeat matches until a mock has been served the given number of times.
Package matchrepeat limits how many times a mock is served: Repeat matches until a mock has been served the given number of times.
matchscenario
Package matchscenario serves mocks in turn, as a scenario moves from state to state.
Package matchscenario serves mocks in turn, as a scenario moves from state to state.
matchscenario/scenariotest
Package scenariotest checks that a mock server serves scenarios as github.com/caffeine-projects/vlatte/match/matchscenario defines them.
Package scenariotest checks that a mock server serves scenarios as github.com/caffeine-projects/vlatte/match/matchscenario defines them.
Package mockgrpc runs gRPC mock servers for tests, and for the vlatte command.
Package mockgrpc runs gRPC mock servers for tests, and for the vlatte command.
internal/protobuf
Package protobuf holds the gRPC service definitions used by the mockgrpc tests.
Package protobuf holds the gRPC service definitions used by the mockgrpc tests.
Package mockhttp runs HTTP mock servers for tests.
Package mockhttp runs HTTP mock servers for tests.
e2e/internal/testcert command
Command testcert writes a self-signed certificate and its key, cert.pem and key.pem, to the working directory.
Command testcert writes a self-signed certificate and its key, cert.pem and key.pem, to the working directory.
internal/mid
Package mid contains utilities to handle middlewares and also common middlewares used internally.
Package mid contains utilities to handle middlewares and also common middlewares used internally.
recipe
http/proxyall
Package proxyall provides a recipe that prepares an HTTP mock server to send the requests no other mock matches to a real service: mock the endpoints a test cares about, and let everything else reach the service as it is.
Package proxyall provides a recipe that prepares an HTTP mock server to send the requests no other mock matches to a real service: mock the endpoints a test cares about, and let everything else reach the service as it is.
Package webhook sends webhooks: HTTP requests a mock makes after it replies.
Package webhook sends webhooks: HTTP requests a mock makes after it replies.

Jump to

Keyboard shortcuts

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