Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

test.snapshot

import test.snapshot

test does not re-export this module, so it is reached only by importing it directly.

Snapshot testing: recording what a value looked like the first time and failing when it stops looking like that.

it('renders the invoice', @{
  expect(render(invoice)).to_match_snapshot()
})

The first run writes the value down. Every run after that compares against what was written and shows a diff when it differs. The file is meant to be committed and reviewed like any other code, because that review is the entire value of the technique: an unexplained change to a snapshot in a pull request is exactly the thing worth noticing.

Where they live

Beside the test file, in a __snapshots__ directory, named after it: tests/invoice.zu gets tests/__snapshots__/invoice.zu.snap.

The file format

Plain text, indented, one entry per snapshot:

# Zuri snapshot file v1

=== Invoice > renders the invoice 1 ===
  {
    total: 4200
  }

The heading is the test’s full name plus the position of the snapshot within that test, or the name given to to_match_snapshot('name'). Every body line is indented two spaces, which is what keeps a value that happens to contain === from being read back as a heading.

Updating

Run with ZURI_UPDATE_SNAPSHOTS=1, or pass { update_snapshots: true } to run(). Every mismatching snapshot is rewritten and reported as updated rather than failed, and entries nothing asked for any more are deleted.

On CI

Writing a brand new snapshot is a pass locally and a failure on CI, because a snapshot nobody has looked at asserts nothing. CI being set in the environment is what switches this on; { ci: false } turns it back off.

Constants

test.snapshot.HEADER = '# Zuri snapshot file v1'

BODY_INDENT

test.snapshot.BODY_INDENT = '  '

Functions

path_for()

test.snapshot.path_for(test_file: string) -> string

The .snap file that belongs to a given test file.

Parameters

  • test_file (string)

Returns string

store_for()

test.snapshot.store_for(test_file: string) -> Store

The store for a test file, read from disk the first time it is asked for and kept afterwards.

Parameters

  • test_file (string)

Returns Store

updating()

test.snapshot.updating() -> bool

Whether snapshots are being rewritten rather than checked.

Returns bool

set_updating()

test.snapshot.set_updating(on: bool)

Turns snapshot rewriting on or off.

Parameters

  • on (bool)

strict()

test.snapshot.strict() -> bool

Whether a brand new snapshot counts as a failure, which is what it should be anywhere nobody is going to look at it before it is committed.

Returns bool

set_strict()

test.snapshot.set_strict(on: bool)

Turns the strict, new-snapshots-fail behaviour on or off.

Parameters

  • on (bool)

check()

test.snapshot.check(test_file: string, key: string, serialized: string)

Compares one value against what the snapshot file holds for key, writing it down when there is nothing there yet.

Parameters

  • test_file (string) — the file the test was declared in.
  • key (string)
  • serialized (string) — the value, already rendered by format.serialize().

Returns — dict: { status, expected, received }, where status is one of 'match', 'written', 'updated', 'mismatch' or 'missing'. 'missing' is the strict-mode answer to what would otherwise have been 'written'.

recorded_lines()

test.snapshot.recorded_lines(test_file: string, key: string) -> list

The lines of the snapshot at key, as a list, for a reporter that wants to show what was recorded.

Parameters

  • test_file (string)
  • key (string)

Returns list

flush()

test.snapshot.flush(prune: ?bool)

Writes every changed store back to disk, and in update mode drops entries the run never asked about.

The runner calls this once, after everything has run. Pruning earlier would delete the snapshots of tests that a filter happened to skip.

Parameters

  • prune (?bool) — whether to drop unused entries. Defaults to whatever updating() says.

summary()

test.snapshot.summary()

What happened to snapshots over the whole run.

Returns — dict: matched, written, updated, obsolete (found and left alone), removed (found and deleted, in update mode), and missing when strict mode refused to write a new one.

reset()

test.snapshot.reset()

Forgets every loaded store and zeroes the counters, so a second run in the same process starts clean.

Classes

Store

class test.snapshot.Store

Every snapshot recorded for one test file, and the file they live in.

Fields

FieldTypeDescription
pathThe .snap file this reads and writes.
entriesSnapshot key to recorded text.
usedKeys this run actually asked about.
dirtyWhether anything has changed since it was read.

Constructor

test.snapshot.Store(path)

Store.read()

test.snapshot.Store.read()

Reads the file, if it is there. A missing file is not an error: it is what every snapshot’s first run looks like.

Store.save()

test.snapshot.Store.save()

Writes the file back, creating the __snapshots__ directory if it is not there yet. Does nothing when nothing changed.

An empty store deletes its file rather than leaving an unexplained stub behind.

Store.prune()

test.snapshot.Store.prune() -> int

Drops every entry this run never asked about, and reports how many there were.

Returns int

Store.obsolete()

test.snapshot.Store.obsolete() -> list

Keys the file holds that this run never asked about.

Returns list