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

vukemtest

package
v0.30.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package vukemtest is Vukem's testing toolkit: boot an app for a test with an isolated in-memory database, send in-process HTTP requests with a fluent client, and assert on responses, sessions and the database.

func TestCreatePost(t *testing.T) {
	app := vukemtest.New(t, bootstrap.New)

	app.Client(t).
		PostForm("/posts", url.Values{"title": {"Hello"}}).
		AssertRedirect("/posts").
		AssertSessionHas("success")

	vukemtest.AssertDatabaseHas(t, app, "posts", map[string]any{"title": "Hello"})
}

New returns an App wrapping the booted app, its handler and fakes (mail through App.Mails, social logins through FakeSocial); a Client sends requests and returns a Response with Assert helpers; FakeClock controls time in tests.

See https://github.com/AlexGalhardo/vukem/blob/main/docs/testing.md.

Example

A test boots the app in process and asserts on its responses.

package main

import (
	"testing"

	vukem "github.com/AlexGalhardo/vukem"
	"github.com/AlexGalhardo/vukem/router"
	"github.com/AlexGalhardo/vukem/vukemtest"
	"github.com/AlexGalhardo/vukem/web"
)

// newApp stands in for an application's bootstrap.New.
func newApp(opts ...vukem.Option) (*vukem.App, error) {
	return vukem.New(append(opts, vukem.WithRoutes(func(r *router.Router, _ *vukem.App) error {
		r.Get("/ping", func(c *web.Ctx) error { return c.JSON(200, map[string]string{"pong": "ok"}) })
		return nil
	}))...)
}

func main() {
	_ = func(t *testing.T) {
		t.Helper()

		app := vukemtest.New(t, newApp)

		app.Client(t).
			GetJSON("/ping").
			AssertOK().
			AssertJSONPath("pong", "ok")
	}
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertDatabaseCount

func AssertDatabaseCount(tb testing.TB, app *App, table string, n int64)

AssertDatabaseCount fails unless table has exactly n rows.

func AssertDatabaseHas

func AssertDatabaseHas(tb testing.TB, app *App, table string, where map[string]any)

AssertDatabaseHas fails unless a row of table matches every column value.

func AssertDatabaseMissing

func AssertDatabaseMissing(tb testing.TB, app *App, table string, where map[string]any)

AssertDatabaseMissing fails if a row of table matches every column value.

func FakeSocial added in v0.9.0

func FakeSocial(u social.User) map[string]string

FakeSocial returns Options.Env entries that make apps scaffolded with install:auth --social sign in u through a fake provider (test apps only).

Types

type App

type App struct {
	*vukem.App
	// contains filtered or unexported fields
}

App is an application booted for one test.

func Build added in v0.24.0

func Build(tb testing.TB, build vukem.BuildFunc, opts ...Options) (*App, error)

Build is New returning the build, boot, migration or route error instead of failing the test, so a test can check that a bad configuration fails closed. It applies the same test defaults.

func New

func New(tb testing.TB, build vukem.BuildFunc, opts ...Options) *App

New builds the app with build (usually bootstrap.New) in the test environment: APP_ENV=test, a random APP_KEY, a private in-memory SQLite database, no HTTP listener. Registered migrations run, and the app shuts down in t.Cleanup. Any build, boot or migration error fails the test; use Build to assert on it instead.

func (*App) Client

func (a *App) Client(tb testing.TB) *Client

Client returns a new client with an empty cookie jar.

func (*App) Handler

func (a *App) Handler() http.Handler

Handler returns the compiled HTTP handler.

func (*App) Mails added in v0.7.0

func (a *App) Mails() *mail.Array

Mails returns the in-memory mail transport (MAIL_MAILER=array, the default in tests) to assert on sent messages.

func (*App) RefreshDatabase

func (a *App) RefreshDatabase(tb testing.TB)

RefreshDatabase drops every table and re-runs the migrations.

type Client

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

Client sends in-process requests to the app and keeps cookies between them, like a browser.

func (*Client) AcceptJSON

func (c *Client) AcceptJSON() *Client

AcceptJSON asks for JSON responses.

func (*Client) ActingAs added in v0.5.0

func (c *Client) ActingAs(guard string, user interface{ AuthID() string }) *Client

ActingAs logs the client in as user on the session guard named guard ("web"), without going through the login form: the session holds what auth.SessionGuard.Login stores (auth.SessionValues), the user's session version included. Values already in the client's session are kept.

func (*Client) CookieValue added in v0.5.0

func (c *Client) CookieValue(name string) string

CookieValue returns the value of a cookie in the jar ("" when absent).

func (*Client) Delete

func (c *Client) Delete(target string) *Response

Delete sends DELETE.

func (*Client) Do

func (c *Client) Do(method, target string, body io.Reader, contentType string) *Response

Do sends a request with an optional body and content type.

func (*Client) Get

func (c *Client) Get(target string) *Response

Get sends GET.

func (*Client) GetJSON

func (c *Client) GetJSON(target string) *Response

GetJSON sends one GET accepting JSON; later requests keep the client's own Accept header (use AcceptJSON to change it for good).

func (*Client) HTMX

func (c *Client) HTMX() *Client

HTMX marks following requests as htmx partial requests.

func (*Client) Inertia added in v0.18.0

func (c *Client) Inertia(version string) *Client

Inertia marks following requests as Inertia visits (X-Inertia, the client's Accept header) with asset version. "" sends the app's current version (app.Inertia.Version()), so tests keep passing after a Vite build; pass another value to test the 409 version-mismatch reload. JSON bodies (PostJSON) then behave like Inertia form submissions, not API calls.

func (*Client) PatchJSON

func (c *Client) PatchJSON(target string, v any) *Response

PatchJSON sends v as JSON with PATCH.

func (*Client) PostForm

func (c *Client) PostForm(target string, form url.Values) *Response

PostForm sends an urlencoded form.

func (*Client) PostJSON

func (c *Client) PostJSON(target string, v any) *Response

PostJSON sends v as JSON. It accepts JSON for this request only when the client sets no Accept header of its own (see GetJSON).

func (*Client) PutJSON

func (c *Client) PutJSON(target string, v any) *Response

PutJSON sends v as JSON with PUT.

func (*Client) WithCookie

func (c *Client) WithCookie(name, value string) *Client

WithCookie adds a cookie to the jar.

func (*Client) WithHeader

func (c *Client) WithHeader(key, value string) *Client

WithHeader sets a header on every following request.

func (*Client) WithSession added in v0.25.0

func (c *Client) WithSession(values map[string]any) *Client

WithSession adds values to the client's session (a login stamp kept by the app, a feature flag...), keeping what it already holds. It starts a new session, as a login would.

func (*Client) WithToken

func (c *Client) WithToken(token string) *Client

WithToken sends "Authorization: Bearer token".

type FakeClock

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

FakeClock is a controllable clock; pass clock.Now as Options.Clock.

Example
package main

import (
	"fmt"
	"time"

	"github.com/AlexGalhardo/vukem/vukemtest"
)

func main() {
	clock := vukemtest.NewFakeClock(time.Date(2026, 10, 16, 9, 0, 0, 0, time.UTC))

	// Pass clock.Now as Options.Clock to drive the app's time.
	clock.Advance(90 * time.Minute)
	fmt.Println(clock.Now().Format(time.Kitchen))
}
Output:
10:30AM

func NewFakeClock

func NewFakeClock(start time.Time) *FakeClock

NewFakeClock returns a clock fixed at start.

func (*FakeClock) Advance

func (c *FakeClock) Advance(d time.Duration)

Advance moves the clock forward.

func (*FakeClock) Now

func (c *FakeClock) Now() time.Time

Now returns the clock's time.

func (*FakeClock) Set

func (c *FakeClock) Set(t time.Time)

Set moves the clock to t.

type InertiaPage added in v0.18.0

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

InertiaPage is the page object of an Inertia response, with chainable prop assertions. Prop paths are dotted ("posts.0.title").

func (*InertiaPage) FlashEquals added in v0.24.0

func (p *InertiaPage) FlashEquals(key string, want any) *InertiaPage

FlashEquals asserts that the page's flash object (inertia.Flash) has key equal to want, compared like PropEquals. It reads only page.flash; use Response.AssertFlash to also find flash data still in the session.

func (*InertiaPage) HasProp added in v0.18.0

func (p *InertiaPage) HasProp(path string, count ...int) *InertiaPage

HasProp asserts that path exists; with count, that it is a list or object of that size.

func (*InertiaPage) MissingFlash added in v0.24.0

func (p *InertiaPage) MissingFlash(key string) *InertiaPage

MissingFlash asserts that the page's flash object has no key. It reads only page.flash; Response.AssertNoFlash also checks the session.

func (*InertiaPage) MissingProp added in v0.18.0

func (p *InertiaPage) MissingProp(path string) *InertiaPage

MissingProp asserts that path does not exist.

func (*InertiaPage) Page added in v0.18.0

func (p *InertiaPage) Page() map[string]any

Page returns the decoded page object.

func (*InertiaPage) Prop added in v0.18.0

func (p *InertiaPage) Prop(path string) (any, bool)

Prop returns the prop at path and whether it exists.

func (*InertiaPage) PropEquals added in v0.18.0

func (p *InertiaPage) PropEquals(path string, want any) *InertiaPage

PropEquals asserts that path equals want, compared after a JSON round trip (so Go structs, ints and maps compare like the decoded page).

type Options

type Options struct {
	// Env adds or overrides configuration values.
	Env map[string]string
	// Clock replaces the app clock (see FakeClock).
	Clock func() time.Time
	// SkipMigrations does not run registered migrations.
	SkipMigrations bool
	// AppOptions are extra vukem options passed to build.
	AppOptions []vukem.Option
}

Options tune New.

type Response

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

Response is a recorded response with chainable assertions.

func (*Response) AssertCreated

func (r *Response) AssertCreated() *Response

AssertCreated asserts 201.

func (*Response) AssertDontSee

func (r *Response) AssertDontSee(text string) *Response

AssertDontSee asserts the body does not contain text.

func (*Response) AssertFlash added in v0.24.0

func (r *Response) AssertFlash(key string, want any) *Response

AssertFlash asserts that key was flashed with a value equal to want (compared by JSON encoding). It finds web flash data (c.Flash) and inertia.Flash data, either still in the session after a redirect or rendered in the "flash" object of an Inertia page. The key must be present: want nil matches a flashed nil, not a missing value. InertiaPage.FlashEquals reads only the page's flash object.

func (*Response) AssertForbidden

func (r *Response) AssertForbidden() *Response

AssertForbidden asserts 403.

func (*Response) AssertFragment

func (r *Response) AssertFragment() *Response

AssertFragment asserts an HTML fragment (no <html> document).

func (*Response) AssertHXTrigger

func (r *Response) AssertHXTrigger(event string) *Response

AssertHXTrigger asserts the response triggers a client event: HX-Trigger is a JSON object holding event, or a comma-separated list of event names ("saved, refresh") that includes it.

func (*Response) AssertHeader

func (r *Response) AssertHeader(key, value string) *Response

AssertHeader asserts a response header value.

func (*Response) AssertHeaderContains added in v0.22.0

func (r *Response) AssertHeaderContains(key, substr string) *Response

AssertHeaderContains checks that header key contains substr.

func (*Response) AssertHeaderMissing added in v0.22.0

func (r *Response) AssertHeaderMissing(key string) *Response

AssertHeaderMissing checks that the response has no header key (for example no Set-Cookie on a health probe, no Server banner).

func (*Response) AssertInertia added in v0.18.0

func (r *Response) AssertInertia(component string) *InertiaPage

AssertInertia checks that the response renders component (as JSON for Inertia visits, or embedded in the HTML of a first visit) and returns the page for prop assertions.

func (*Response) AssertJSON

func (r *Response) AssertJSON(want any) *Response

AssertJSON asserts the whole body equals want (by JSON encoding).

func (*Response) AssertJSONCount

func (r *Response) AssertJSONCount(path string, n int) *Response

AssertJSONCount asserts the array (or object) at path has n entries.

func (*Response) AssertJSONPath

func (r *Response) AssertJSONPath(path string, want any) *Response

AssertJSONPath asserts the value at path equals want (compared by its JSON encoding, so 3 matches json.Number("3")).

func (*Response) AssertJSONValidationErrors

func (r *Response) AssertJSONValidationErrors(fields ...string) *Response

AssertJSONValidationErrors asserts a 422 problem with errors for fields.

func (*Response) AssertMatchesOpenAPI added in v0.15.0

func (r *Response) AssertMatchesOpenAPI() *Response

AssertMatchesOpenAPI checks the response against the app's OpenAPI document (App.OpenAPI): the operation for the request, the schema of the response status (or the default problem response).

func (*Response) AssertNoContent

func (r *Response) AssertNoContent() *Response

AssertNoContent asserts 204.

func (*Response) AssertNoFlash added in v0.24.0

func (r *Response) AssertNoFlash(key string) *Response

AssertNoFlash asserts that key is not flashed, neither in the session nor in an Inertia page's flash object.

func (*Response) AssertNotFound

func (r *Response) AssertNotFound() *Response

AssertNotFound asserts 404.

func (*Response) AssertOK

func (r *Response) AssertOK() *Response

AssertOK asserts 200.

func (*Response) AssertProblem

func (r *Response) AssertProblem(status int) *Response

AssertProblem asserts an RFC 9457 problem+json response with status.

func (*Response) AssertRedirect

func (r *Response) AssertRedirect(location string) *Response

AssertRedirect asserts a 3xx (or htmx HX-Location) to location.

func (*Response) AssertRedirectRoute

func (r *Response) AssertRedirectRoute(name string, params ...any) *Response

AssertRedirectRoute asserts a redirect to a named route.

func (*Response) AssertSee

func (r *Response) AssertSee(text string) *Response

AssertSee asserts the body contains text.

func (*Response) AssertSessionHas

func (r *Response) AssertSessionHas(key string) *Response

AssertSessionHas asserts a session (or flash) key is set. A key stored with a nil value is set.

func (*Response) AssertSessionHasErrors

func (r *Response) AssertSessionHasErrors(fields ...string) *Response

AssertSessionHasErrors asserts validation errors were flashed for fields.

func (*Response) AssertSessionMissing

func (r *Response) AssertSessionMissing(key string) *Response

AssertSessionMissing asserts a session key is absent; a key stored with a nil value is not absent.

func (*Response) AssertStatus

func (r *Response) AssertStatus(code int) *Response

AssertStatus fails unless the status is code.

func (*Response) AssertUnauthorized

func (r *Response) AssertUnauthorized() *Response

AssertUnauthorized asserts 401.

func (*Response) AssertUnprocessable

func (r *Response) AssertUnprocessable() *Response

AssertUnprocessable asserts 422.

func (*Response) Body

func (r *Response) Body() string

Body returns the body.

func (*Response) Header

func (r *Response) Header(key string) string

Header returns a response header.

func (*Response) JSON added in v0.30.0

func (r *Response) JSON(v any) *Response

JSON decodes the body into v, which must be a pointer, and fails the test when it does not decode. Use it for a typed assertion on a response, instead of json.Unmarshal([]byte(res.Body()), &v): the error branch that call needs is easy to leave out, and without it a body that did not decode reads as a zero value and the test carries on asserting against it.

Unknown fields are ignored, like encoding/json: assert on what the handler promises, not on the absence of what it does not.

func (*Response) JSONPath

func (r *Response) JSONPath(path string) (any, bool)

JSONPath returns the value at a dotted path ("data.0.title").

func (*Response) Recorder

func (r *Response) Recorder() *httptest.ResponseRecorder

Recorder exposes the raw recorder.

func (*Response) Session

func (r *Response) Session() map[string]any

Session returns the session data the next request would see.

func (*Response) Status

func (r *Response) Status() int

Status returns the status code.

Jump to

Keyboard shortcuts

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