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")
}
}
Output:
Index ¶
- func AssertDatabaseCount(tb testing.TB, app *App, table string, n int64)
- func AssertDatabaseHas(tb testing.TB, app *App, table string, where map[string]any)
- func AssertDatabaseMissing(tb testing.TB, app *App, table string, where map[string]any)
- func FakeSocial(u social.User) map[string]string
- type App
- type Client
- func (c *Client) AcceptJSON() *Client
- func (c *Client) ActingAs(guard string, user interface{ ... }) *Client
- func (c *Client) CookieValue(name string) string
- func (c *Client) Delete(target string) *Response
- func (c *Client) Do(method, target string, body io.Reader, contentType string) *Response
- func (c *Client) Get(target string) *Response
- func (c *Client) GetJSON(target string) *Response
- func (c *Client) HTMX() *Client
- func (c *Client) Inertia(version string) *Client
- func (c *Client) PatchJSON(target string, v any) *Response
- func (c *Client) PostForm(target string, form url.Values) *Response
- func (c *Client) PostJSON(target string, v any) *Response
- func (c *Client) PutJSON(target string, v any) *Response
- func (c *Client) WithCookie(name, value string) *Client
- func (c *Client) WithHeader(key, value string) *Client
- func (c *Client) WithSession(values map[string]any) *Client
- func (c *Client) WithToken(token string) *Client
- type FakeClock
- type InertiaPage
- func (p *InertiaPage) FlashEquals(key string, want any) *InertiaPage
- func (p *InertiaPage) HasProp(path string, count ...int) *InertiaPage
- func (p *InertiaPage) MissingFlash(key string) *InertiaPage
- func (p *InertiaPage) MissingProp(path string) *InertiaPage
- func (p *InertiaPage) Page() map[string]any
- func (p *InertiaPage) Prop(path string) (any, bool)
- func (p *InertiaPage) PropEquals(path string, want any) *InertiaPage
- type Options
- type Response
- func (r *Response) AssertCreated() *Response
- func (r *Response) AssertDontSee(text string) *Response
- func (r *Response) AssertFlash(key string, want any) *Response
- func (r *Response) AssertForbidden() *Response
- func (r *Response) AssertFragment() *Response
- func (r *Response) AssertHXTrigger(event string) *Response
- func (r *Response) AssertHeader(key, value string) *Response
- func (r *Response) AssertHeaderContains(key, substr string) *Response
- func (r *Response) AssertHeaderMissing(key string) *Response
- func (r *Response) AssertInertia(component string) *InertiaPage
- func (r *Response) AssertJSON(want any) *Response
- func (r *Response) AssertJSONCount(path string, n int) *Response
- func (r *Response) AssertJSONPath(path string, want any) *Response
- func (r *Response) AssertJSONValidationErrors(fields ...string) *Response
- func (r *Response) AssertMatchesOpenAPI() *Response
- func (r *Response) AssertNoContent() *Response
- func (r *Response) AssertNoFlash(key string) *Response
- func (r *Response) AssertNotFound() *Response
- func (r *Response) AssertOK() *Response
- func (r *Response) AssertProblem(status int) *Response
- func (r *Response) AssertRedirect(location string) *Response
- func (r *Response) AssertRedirectRoute(name string, params ...any) *Response
- func (r *Response) AssertSee(text string) *Response
- func (r *Response) AssertSessionHas(key string) *Response
- func (r *Response) AssertSessionHasErrors(fields ...string) *Response
- func (r *Response) AssertSessionMissing(key string) *Response
- func (r *Response) AssertStatus(code int) *Response
- func (r *Response) AssertUnauthorized() *Response
- func (r *Response) AssertUnprocessable() *Response
- func (r *Response) Body() string
- func (r *Response) Header(key string) string
- func (r *Response) JSON(v any) *Response
- func (r *Response) JSONPath(path string) (any, bool)
- func (r *Response) Recorder() *httptest.ResponseRecorder
- func (r *Response) Session() map[string]any
- func (r *Response) Status() int
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AssertDatabaseCount ¶
AssertDatabaseCount fails unless table has exactly n rows.
func AssertDatabaseHas ¶
AssertDatabaseHas fails unless a row of table matches every column value.
func AssertDatabaseMissing ¶
AssertDatabaseMissing fails if a row of table matches every column value.
Types ¶
type App ¶
App is an application booted for one test.
func Build ¶ added in v0.24.0
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 ¶
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) Mails ¶ added in v0.7.0
Mails returns the in-memory mail transport (MAIL_MAILER=array, the default in tests) to assert on sent messages.
func (*App) RefreshDatabase ¶
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 ¶
AcceptJSON asks for JSON responses.
func (*Client) ActingAs ¶ added in v0.5.0
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
CookieValue returns the value of a cookie in the jar ("" when absent).
func (*Client) GetJSON ¶
GetJSON sends one GET accepting JSON; later requests keep the client's own Accept header (use AcceptJSON to change it for good).
func (*Client) Inertia ¶ added in v0.18.0
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) PostJSON ¶
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) WithCookie ¶
WithCookie adds a cookie to the jar.
func (*Client) WithHeader ¶
WithHeader sets a header on every following request.
func (*Client) WithSession ¶ added in v0.25.0
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.
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 ¶
NewFakeClock returns a clock fixed at start.
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 ¶
AssertCreated asserts 201.
func (*Response) AssertDontSee ¶
AssertDontSee asserts the body does not contain text.
func (*Response) AssertFlash ¶ added in v0.24.0
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 ¶
AssertForbidden asserts 403.
func (*Response) AssertFragment ¶
AssertFragment asserts an HTML fragment (no <html> document).
func (*Response) AssertHXTrigger ¶
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 ¶
AssertHeader asserts a response header value.
func (*Response) AssertHeaderContains ¶ added in v0.22.0
AssertHeaderContains checks that header key contains substr.
func (*Response) AssertHeaderMissing ¶ added in v0.22.0
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 ¶
AssertJSON asserts the whole body equals want (by JSON encoding).
func (*Response) AssertJSONCount ¶
AssertJSONCount asserts the array (or object) at path has n entries.
func (*Response) AssertJSONPath ¶
AssertJSONPath asserts the value at path equals want (compared by its JSON encoding, so 3 matches json.Number("3")).
func (*Response) AssertJSONValidationErrors ¶
AssertJSONValidationErrors asserts a 422 problem with errors for fields.
func (*Response) AssertMatchesOpenAPI ¶ added in v0.15.0
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 ¶
AssertNoContent asserts 204.
func (*Response) AssertNoFlash ¶ added in v0.24.0
AssertNoFlash asserts that key is not flashed, neither in the session nor in an Inertia page's flash object.
func (*Response) AssertNotFound ¶
AssertNotFound asserts 404.
func (*Response) AssertProblem ¶
AssertProblem asserts an RFC 9457 problem+json response with status.
func (*Response) AssertRedirect ¶
AssertRedirect asserts a 3xx (or htmx HX-Location) to location.
func (*Response) AssertRedirectRoute ¶
AssertRedirectRoute asserts a redirect to a named route.
func (*Response) AssertSessionHas ¶
AssertSessionHas asserts a session (or flash) key is set. A key stored with a nil value is set.
func (*Response) AssertSessionHasErrors ¶
AssertSessionHasErrors asserts validation errors were flashed for fields.
func (*Response) AssertSessionMissing ¶
AssertSessionMissing asserts a session key is absent; a key stored with a nil value is not absent.
func (*Response) AssertStatus ¶
AssertStatus fails unless the status is code.
func (*Response) AssertUnauthorized ¶
AssertUnauthorized asserts 401.
func (*Response) AssertUnprocessable ¶
AssertUnprocessable asserts 422.
func (*Response) JSON ¶ added in v0.30.0
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) Recorder ¶
func (r *Response) Recorder() *httptest.ResponseRecorder
Recorder exposes the raw recorder.