| name | syrupy |
| description | Use syrupy for pytest snapshot testing to ensure the immutability of computed results, manage snapshots, customize serialization, and handle complex data structures with built-in matchers and filters. |
Syrupy - Pytest Snapshot Testing
Syrupy is a zero-dependency pytest snapshot testing plugin that enables asserting the immutability of computed results through simple, idiomatic assertions.
Docs: https://syrupy-project.github.io/syrupy/
What is Syrupy?
Syrupy is a snapshot testing library that:
- Captures the output/state of code at a point in time
- Compares future test runs against saved snapshots
- Automatically manages snapshot files in
__snapshots__directories - Integrates naturally with pytest's assertion syntax
Core Philosophy
Three Design Principles:
- Extensible: Easy to add support for custom/unsupported data types
- Idiomatic: Natural pytest-style assertions (
assert x == snapshot) - Sound: Fails tests if snapshots are missing or different
Target Use Case: Testing complex data structures, API responses, UI components, or any computed results that should remain stable over time.
Snapshot Fixture API
Core Methods
The snapshot fixture accepts several options per assertion:
matcher: Control how objects are serializedexclude: Filter out properties from snapshotsinclude: Include only specific propertiesextension_class: Use a different serialization formatdiff: Capture only differences from a basename: Custom name for the snapshot
Usage with Options
assert data == snapshot(matcher=my_matcher, exclude=my_filter, name="custom_name")
Built-in Extensions
Syrupy provides several snapshot extensions for different use cases:
AmberSnapshotExtension (Default)
- Human-readable format
- Stores all snapshots in
.ambrfiles - Supports all Python built-in types
- Custom object representation via
__repr__
JSONSnapshotExtension
- Stores snapshots as JSON files
- Useful for API responses
- Machine-readable format
SingleFileSnapshotExtension
- One file per snapshot
- Configurable file extensions
- Useful for binary data or large snapshots
PNGSnapshotExtension
- For image snapshot testing
- Compares PNG image data
SVGSnapshotExtension
- For SVG vector graphics
- Text-based comparison of SVG content
Matchers
Matchers control how specific values are serialized during snapshot creation.
path_type
Match specific paths in data structures to types:
from syrupy.matchers import path_type
import datetime
matcher = path_type(
{"date_created": (datetime,), "user.id": (int,), "nested.*.timestamp": (datetime,)}
)
path_value
Match paths and replace with specific values:
from syrupy.matchers import path_value
matcher = path_value({"id": "REDACTED_ID", "token": "***"})
Filters
Filters control which properties are included/excluded from snapshots.
props
Filter by property names (shallow):
from syrupy.filters import props
# Exclude specific properties
exclude = props("id", "timestamp", "random_value")
# Include only specific properties
include = props("name", "type", "data")
paths
Filter by full property paths:
from syrupy.filters import paths
# Exclude nested paths
exclude = paths("user.password", "response.headers.authorization", "items.*.id")
CLI Options
Syrupy adds several pytest command-line options:
--snapshot-update: Update snapshots with current values--snapshot-warn-unused: Warn about unused snapshots--snapshot-details: Show detailed snapshot information--snapshot-default-extension: Change default extension class--snapshot-no-colors: Disable colored output
Advanced Configuration
Custom Snapshot Names
def test_multiple_cases(snapshot):
assert case_1 == snapshot(name="case_1")
assert case_2 == snapshot(name="case_2")
Note: Custom names must be unique within a test function.
Persistent Configuration
Create a snapshot instance with default values:
def test_api_responses(snapshot):
api_snapshot = snapshot.with_defaults(
extension_class=JSONSnapshotExtension, exclude=props("timestamp", "request_id")
)
assert response1 == api_snapshot
assert response2 == api_snapshot # Uses same defaults
Custom Extensions
Create custom snapshot serializers by extending AbstractSnapshotExtension:
from syrupy.extensions.base import AbstractSnapshotExtension
class MyExtension(AbstractSnapshotExtension):
def serialize(self, data, **kwargs):
# Custom serialization logic
return str(data)
def matches(self, *, serialized_data, snapshot_data):
# Custom comparison logic
return serialized_data == snapshot_data
Snapshot Lifecycle
Creation Flow
- Run test without existing snapshot
- Test fails with "snapshot does not exist"
- Run with
--snapshot-updateto create - Snapshot file created in
__snapshots__/ - Commit snapshot to version control
Update Flow
- Code changes cause snapshot mismatch
- Test fails showing difference
- Review changes to ensure correctness
- Run with
--snapshot-updateif changes are expected - Commit updated snapshot
Cleanup
Remove unused snapshots:
pytest --snapshot-update --snapshot-warn-unused
Data Type Support
Built-in Types
All Python built-in types are supported:
- Primitives:
int,float,str,bool,None - Collections:
list,tuple,set,dict - Complex:
datetime,bytes, custom objects (via__repr__)
Custom Objects
Options for custom object snapshots:
- Override
__repr__method - Use custom matcher
- Create custom extension
- Use exclude/include filters
Best Practices
DO
- Commit snapshots to version control: They're part of your test suite
- Review snapshot changes carefully: Ensure changes are intentional
- Use meaningful test names: Helps identify snapshot purpose
- Keep snapshots focused: Test one thing per snapshot
- Use matchers for non-deterministic data: Dates, IDs, timestamps
DON'T
- Don't snapshot entire responses blindly: Filter out volatile data
- Don't ignore snapshot changes: They indicate behavior changes
- Don't use generic test names: Makes debugging harder
- Don't snapshot huge data structures: Use filters or separate tests
- Don't update snapshots without review: Verify changes are correct
Common Patterns
API Response Testing
Use JSON extension with filters:
@pytest.fixture
def api_snapshot(snapshot):
return snapshot.use_extension(JSONSnapshotExtension).with_defaults(
exclude=props("timestamp", "request_id", "session")
)
Dynamic Data Handling
Use matchers for non-deterministic values:
from syrupy.matchers import path_type
import uuid
import datetime
assert response == snapshot(
matcher=path_type(
{
"id": (uuid.UUID,),
"created_at": (datetime.datetime,),
"*.timestamp": (datetime.datetime,),
}
)
)
Diff-Based Snapshots
Capture only changes from a baseline:
def test_incremental_changes(snapshot):
baseline = {"config": {...}}
modified = apply_changes(baseline)
assert modified == snapshot(diff=baseline)
Important Constraints
- Python/pytest versions: Requires Python 3.10+ and pytest 8+
- Snapshot immutability: Never edit snapshot files manually
- Name uniqueness: Custom snapshot names must be unique per test
- Path separators: Use dots for nested paths in filters/matchers
- Zero dependencies: Syrupy has no external dependencies
Troubleshooting
Common Issues
- Snapshot not found: Run with
--snapshot-update - Unexpected differences: Check for non-deterministic data
- Large diffs: Use filters to focus on relevant data
- Flaky tests: Use matchers for dynamic values
- Merge conflicts: Update snapshots after resolving
Debug Options
- Use
--snapshot-detailsfor verbose output - Check
__snapshots__/directory for actual files - Use
excludeto isolate problematic fields - Test with smaller data sets first
Migration from Other Libraries
From pytest-snapshot
- Similar API, minimal changes needed
- Update import statements
- Regenerate snapshots
From snapshottest
- Change
snapshot.assert_match()toassert x == snapshot - Update fixture name if customized
- Regenerate all snapshots