Documentation
¶
Overview ¶
Package snap is a snapshot-testing library for text: CLI output, rendered documents, language-server responses, interactive terminal sessions.
A suite is declared as data - which sections its files may contain, and which output channels its runner reports - plus one function that drives the system under test. The library owns everything else: discovering files, parsing them, running a subtest per case, comparing, diffing, and rewriting expectations when you accept them.
var suite = snap.Suite{
Run: runCase,
Inputs: []snap.Input{{Name: "ARGS", List: true, Quoted: true}},
Outputs: []snap.Output{{Name: "STDOUT"}, {Name: "EXIT", Int: true}},
}
func TestSnapshots(t *testing.T) { snap.Run(t, "snapshots", &suite) }
func runCase(t *testing.T, c *snap.Case) {
stdout, exit := runCLI(c.List("ARGS"))
c.Out("STDOUT", stdout)
c.OutInt("EXIT", exit)
}
Files ¶
One file holds many cases, so related behavior is reviewed together:
### TITLE ### rejects an unknown flag ### ARGS ### --nope ### STDERR ### unknown flag: --nope ### EXIT ### 2
Sections a suite never declared are an error, not content: a mistyped delimiter should fail loudly rather than quietly become expected output that asserts nothing.
Cases with steps ¶
An Action is a section that may repeat, carry header arguments, and produce its own output - enough to snapshot a session rather than a single call:
### HOVER 1:6 ###
### RESULT ###
`foo`: int
### RENAME 0:0 bar ###
### RESULT ###
{"changes": 2}
The single-shot case above is the same model with no steps, so a suite can grow into sequences without changing shape.
Accepting new output ¶
Snapshot updates are targeted:
go test ./... -update=errors/validation
Only matching files are rewritten; a mismatch anywhere else still fails, so accepting one change cannot quietly absorb an unrelated regression. Use -update-all for a deliberate sweep, or SNAP_UPDATE=<substr> when the flag cannot reach the test binary. Every failure prints the exact command that would accept it.
Determinism ¶
The library does not scrub timestamps, paths or IDs. Suites that inject a fixed clock and deterministic IDs get stable snapshots by construction, and keep the real values visible in the file; scrubbing is a last resort, applied in the runner before the value reaches a channel.
Index ¶
- Constants
- func Run(t *testing.T, dir string, s *Suite)
- func WriteFile(s *Suite, path string, cases []*Case) error
- type Action
- type Case
- func (c *Case) Description() string
- func (c *Case) Expected(channel string) (string, bool)
- func (c *Case) ExpectedInt(channel string) (int, bool)
- func (c *Case) Has(section string) bool
- func (c *Case) List(section string) []string
- func (c *Case) Out(channel, value string)
- func (c *Case) OutInt(channel string, value int)
- func (c *Case) Steps() []*Step
- func (c *Case) Text(section string) string
- func (c *Case) Title() string
- type DiffConfig
- type Input
- type Markers
- type Output
- type RunFunc
- type Step
- type Suite
Constants ¶
const ( Ext = ".snap" PendingExt = ".snap.new" )
Ext is the snapshot file extension. PendingExt is reserved: files carrying it are never discovered as suite files, leaving room for a review workflow that writes proposed output beside the accepted file rather than over it.
Variables ¶
This section is empty.
Functions ¶
func Run ¶
Run executes every .snap file under dir as subtests of t.
Each case becomes a subtest named "<file>/<title>", so `-run` can select one file or one case. Under -update, a file is rewritten once all of its cases have finished.
Types ¶
type Action ¶
type Action struct {
Name string
// Args is the exact number of header argument tokens, or -1 for variadic.
Args int
// HasBody allows a body under the header. An action without it rejects
// one; an action with it may still be written empty, since "no content"
// is usually a case worth testing rather than a mistake.
HasBody bool
}
Action is a repeatable hand-authored section. Each occurrence in a case is one step, in file order, and may carry inline header arguments:
### HOVER 2:5 ###
Argument syntax beyond tokenization belongs to the suite: the engine splits and counts tokens, the runner interprets them.
type Case ¶
type Case struct {
// contains filtered or unexported fields
}
Case is one snapshot case: its hand-authored inputs and steps, and the output channels the runner sets.
func ParseFile ¶
ParseFile reads a .snap file against a suite's schema. It is exported for tooling - editors, migration scripts, orphan reports - that wants to read snapshot files without running them.
func (*Case) Description ¶
Description is the case's DESCRIPTION, or "" if it has none. It is prose for humans; the engine never interprets it.
func (*Case) Expected ¶
Expected returns what the case's file records for an output channel, and whether the file carries that section at all.
Absent is not the same as unasserted: a missing section asserts the channel's zero value. The flag exists so a tool can tell the two apart, since only one of them has a line to rewrite. Suites do not need this - a runner reports what the system did and lets the engine compare - but a formatter, a linter, or a review tool reading a pending file does.
func (*Case) ExpectedInt ¶
ExpectedInt is Expected for an Int channel.
func (*Case) Has ¶
Has reports whether the case declares the given input section at all, distinguishing a section present but empty from one that is absent.
func (*Case) Out ¶
Out records a case-scoped output channel. Setting a channel asserts it; leaving it unset asserts nothing. Setting it to "" asserts that it is empty.
type DiffConfig ¶
type DiffConfig struct {
// Width of the rendered diff. Default 120.
Width int
// Color forces color on or off. Nil auto-detects, honoring NO_COLOR and
// SNAP_NO_COLOR.
Color *bool
// Func replaces the renderer entirely.
Func func(want, got string) string
}
DiffConfig tunes how a mismatch is rendered. The goal a suite should hold it to: a failure is diagnosable from the test output alone, without opening the snapshot file to compare by hand.
type Input ¶
type Input struct {
Name string
// List parses the body as one item per non-blank line, whitespace trimmed.
List bool
// Quoted lets a List item be written as a Go-quoted string, which is the
// only way to express an empty or whitespace-significant item.
Quoted bool
// Raw stores the body as a single Go-quoted string, preserving bytes
// exactly - trailing whitespace, CRs and all.
Raw bool
}
Input is a unique hand-authored section carrying a body.
type Markers ¶
Markers is the section header syntax. A header line is Prefix + NAME + (" " + arg)... + Suffix, alone on its line.
type Output ¶
type Output struct {
Name string
// PerStep correlates the channel with a step rather than the case, and
// writes it directly under that step's header.
PerStep bool
// Int stores an integer; absent means zero.
Int bool
// Raw compares bytes exactly rather than forgiving trailing newlines, and
// stores the value as a single Go-quoted string.
Raw bool
}
Output is a machine-generated channel. The runner sets it; -update regenerates it from what the runner set.
type RunFunc ¶
RunFunc drives the system under test for one case. It reads the case's hand-authored inputs and steps, and reports results by setting output channels. It never sees expected values: comparison is the engine's job, so a runner cannot accidentally become the assertion.
type Step ¶
type Step struct {
// Name is the action's section name.
Name string
// Args are the header argument tokens, already split.
Args []string
// Body is the section body, empty for header-only actions.
Body string
// contains filtered or unexported fields
}
Step is one occurrence of an Action within a case.
func (*Step) Expected ¶
Expected returns what the file records for a per-step channel on this step, and whether the section is present.
func (*Step) ExpectedInt ¶
ExpectedInt is Expected for an Int channel.
type Suite ¶
type Suite struct {
Run RunFunc
Inputs []Input
Actions []Action
Outputs []Output
// Markers sets the section header syntax. The zero value is "### NAME ###",
// chosen to avoid collision with most host-language syntax; override when
// the content you snapshot would fight it.
Markers Markers
// Parallel runs each case with t.Parallel. Opt in only when the runner
// holds no cross-case global state - the engine's own bookkeeping is
// parallel-safe either way.
Parallel bool
Diff DiffConfig
}
Suite declares a snapshot suite: its section schema, its runner, and its options. A schema is a table, so declare it as a struct literal.
The library owns TITLE, DESCRIPTION and SKIP. Everything else is yours: Inputs are unique hand-authored sections, Actions are repeatable ordered ones forming a case's step sequence, and Outputs are machine-generated channels regenerated under -update.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package example holds two toy systems and the snapshot suites that test them.
|
Package example holds two toy systems and the snapshot suites that test them. |
|
Package prompt snapshots interactive terminal prompts.
|
Package prompt snapshots interactive terminal prompts. |