Snapshots
Snapshot testing captures the output of your code and stores it in a file. On subsequent runs, the output is compared against the stored snapshot. If the output changes, the test fails with a diff showing what changed.
This is useful for testing complex outputs like formatted strings, serialized data, or API responses without writing manual assertions.
Basic Usage
Use karva.assert_snapshot() to capture a value as a snapshot.
| test.py | |
|---|---|
1 2 3 4 | |
The first time you run this test, it will fail and create a pending snapshot file at snapshots/test__test_greeting.snap.new. Accept it to create the baseline:
| Bash | |
|---|---|
1 | |
On subsequent runs, the test passes as long as the output matches the stored snapshot.
JSON Snapshots
For structured data like dicts and lists, use karva.assert_json_snapshot() for readable, deterministic output. It serializes values using json.dumps(value, sort_keys=True, indent=2).
| test.py | |
|---|---|
1 2 3 4 5 | |
The snapshot stores:
| JSON | |
|---|---|
1 2 3 4 5 6 7 | |
assert_json_snapshot supports all the same features as assert_snapshot: inline snapshots, --snapshot-update, filters via snapshot_settings, and the pending/accept workflow.
| test.py | |
|---|---|
1 2 3 4 | |
If the value is not JSON-serializable (e.g., a custom object without a default serializer), Python's json module raises a TypeError.
Command Snapshots
Use karva.Command to capture the stdout, stderr, and exit code of an external command as a snapshot.
| test.py | |
|---|---|
1 2 3 4 5 6 | |
The snapshot stores:
| Text Only | |
|---|---|
1 2 3 4 5 | |
Builder API
karva.Command uses a builder pattern. All methods return self for chaining:
| test.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 | |
Available methods:
arg(value)— append a single argumentargs(values)— append a list of argumentsenv(key, value)— set an environment variableenvs(vars)— set multiple environment variables from a dictcurrent_dir(path)— set the working directorystdin(data)— pass a string to the command's stdin
stdin
| test.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 | |
assert_cmd_snapshot supports name=, inline=, filters via snapshot_settings, and the pending/accept workflow, just like assert_snapshot.
Named Snapshots
By default, each snapshot is named after the test function. If a test contains more than one unnamed assert_snapshot() call, karva raises an error:
| Text Only | |
|---|---|
1 2 | |
Use the name parameter to give each snapshot a distinct name:
| test.py | |
|---|---|
1 2 3 4 5 6 | |
This creates three separate snapshot files:
snapshots/test__test_page--header.snapsnapshots/test__test_page--body.snapsnapshots/test__test_page--footer.snap
Alternatively, wrap the calls in snapshot_settings(allow_duplicates=True) to opt in to auto-numbered unnamed snapshots (test_page-0, test_page-1, test_page-2):
| test.py | |
|---|---|
1 2 3 4 5 6 7 | |
Snapshot Files
Snapshot files are stored in a snapshots/ directory next to your test file. Each file uses YAML frontmatter to record metadata:
| Text Only | |
|---|---|
1 2 3 4 | |
The source field records the file, line number, and test name that produced the snapshot.
When a test produces a new or changed snapshot, a .snap.new file is created alongside the existing .snap file. This pending file must be explicitly accepted or rejected before the test will pass.
Inline Snapshots
Instead of storing expected values in separate .snap files, inline snapshots embed them directly in your test source file using the inline= parameter.
| test.py | |
|---|---|
1 2 3 4 | |
To create a new inline snapshot, pass an empty string and run with --snapshot-update:
| test.py | |
|---|---|
1 2 3 4 | |
| Bash | |
|---|---|
1 | |
Karva rewrites your source file, replacing inline="" with the actual value.
Multiline Values
For multiline values, Karva generates a triple-quoted string:
| test.py | |
|---|---|
1 2 3 4 5 6 7 8 | |
Accept Workflow
When a pending inline snapshot is accepted with karva snapshot accept, Karva rewrites the inline= argument in your source file in place. No separate .snap file is created.
All three assertion functions support inline=:
karva.assert_snapshot(value, inline="")karva.assert_json_snapshot(value, inline="")karva.assert_cmd_snapshot(cmd, inline="")
Updating Snapshots
When you intentionally change the output of your code, use --snapshot-update to update all snapshots in place without creating pending files:
| Bash | |
|---|---|
1 | |
This writes directly to .snap files and the tests pass immediately.
CLI Commands
The karva snapshot subcommand manages pending snapshots.
accept
Accept all pending snapshots, promoting .snap.new files to .snap:
| Bash | |
|---|---|
1 | |
reject
Reject all pending snapshots, deleting the .snap.new files:
| Bash | |
|---|---|
1 | |
pending
List all pending snapshots:
| Bash | |
|---|---|
1 | |
review
Interactively review each pending snapshot one at a time:
| Bash | |
|---|---|
1 | |
For each snapshot, you can:
- a -- accept (keep the new snapshot)
- r -- reject (retain the old snapshot)
- s -- skip (keep both for now)
- i -- toggle extended info display
- d -- toggle diff display
Use uppercase A, R, or S to apply the action to all remaining snapshots.
All commands accept optional path arguments to filter which snapshots are affected:
| Bash | |
|---|---|
1 2 | |
prune
Remove snapshot files whose source test no longer exists. This uses static analysis to detect deleted or renamed test functions and files.
| Bash | |
|---|---|
1 | |
Use --dry-run to preview what would be removed:
| Bash | |
|---|---|
1 | |
delete
Delete all snapshot files (both .snap and .snap.new):
| Bash | |
|---|---|
1 | |
Use --dry-run to preview what would be deleted:
| Bash | |
|---|---|
1 | |
Both prune and delete accept optional path arguments to limit their scope:
| Bash | |
|---|---|
1 2 | |
Parametrized Tests
Snapshot testing works with parametrized tests. Each parameter combination gets its own snapshot file.
| test.py | |
|---|---|
1 2 3 4 5 | |
This creates:
snapshots/test__test_greet(name=Alice).snapsnapshots/test__test_greet(name=Bob).snap
Named snapshots in parametrized tests combine both:
| test.py | |
|---|---|
1 2 3 4 5 | |
This creates:
snapshots/test__test_translate--greeting(lang=en).snapsnapshots/test__test_translate--greeting(lang=fr).snap
Filters
Snapshot output often contains non-deterministic values like timestamps, UUIDs, or file paths that change between runs. Use karva.snapshot_settings() to replace these with stable placeholders before comparison.
| test.py | |
|---|---|
1 2 3 4 5 6 7 8 | |
Each filter is a (regex_pattern, replacement) tuple. Filters are applied sequentially to the serialized value before it is compared or stored in the snapshot file.
Multiple Filters
When multiple filters are provided, they are applied in order. Earlier filters may affect what later filters see:
| test.py | |
|---|---|
1 2 3 4 5 6 7 8 | |
The stored snapshot will contain: [date]: request completed in [duration].
Nested Settings
Settings can be nested. Inner filters are appended to outer filters, so all filters from the entire stack apply:
| test.py | |
|---|---|
1 2 3 4 5 6 | |
The stored snapshot will contain: took [duration] at [path].
Inline Snapshots
Filters also work with inline snapshots. The filtered value is what gets compared and stored:
| test.py | |
|---|---|
1 2 3 4 5 | |