Pytest snapshot testing utility that enables developers to write tests asserting immutability of computed results.
Overall
score
80%
Syrupy's extensible serialization and storage system supporting multiple output formats. Extensions determine how data is serialized and where snapshots are stored, allowing customization for different data types and use cases.
Abstract base classes for creating custom extensions with serialization, storage, and comparison capabilities.
class AbstractSyrupyExtension:
"""
Base class combining serialization, storage, and reporting.
All extension classes should inherit from this base.
"""
def get_snapshot_name(self, *, index: SnapshotIndex = 0, **kwargs) -> str:
"""
Generate snapshot name for given index.
Parameters:
- index: Snapshot index (int or str)
- **kwargs: Additional naming options
Returns:
str: Generated snapshot name
"""
def get_location(self, *, test_location: "PyTestLocation") -> str:
"""
Get storage location for snapshots.
Parameters:
- test_location: Test file location info
Returns:
str: Storage location path
"""
def discover_snapshots(self, *, location: str) -> "SnapshotCollections":
"""
Discover existing snapshots at location.
Parameters:
- location: Location to search
Returns:
SnapshotCollections: Found snapshot collections
"""
class SnapshotSerializer:
def serialize(self, data: SerializableData, **kwargs) -> SerializedData:
"""
Serialize data for snapshot storage.
Parameters:
- data: Data to serialize
- **kwargs: Additional serialization options
Returns:
SerializedData: Serialized representation (str or bytes)
"""
class SnapshotCollectionStorage:
def read_snapshot(self, snapshot_location: str, snapshot_name: str) -> SerializedData:
"""
Read snapshot data from storage.
Parameters:
- snapshot_location: Location identifier for snapshot
- snapshot_name: Name identifier for specific snapshot
Returns:
SerializedData: Stored snapshot data
"""
def write_snapshot(self, snapshot_data: SerializedData, snapshot_location: str, snapshot_name: str) -> None:
"""
Write snapshot data to storage.
Parameters:
- snapshot_data: Serialized data to store
- snapshot_location: Location identifier for snapshot
- snapshot_name: Name identifier for specific snapshot
"""Default multi-snapshot extension creating .ambr files with comprehensive Python object serialization.
class AmberSnapshotExtension(AbstractSyrupyExtension):
"""
Default extension creating .ambr files with multiple snapshots per file.
Features:
- Multi-snapshot storage in single file
- Deep object introspection and serialization
- Support for most Python data types
- Human-readable text format
"""
class AmberDataSerializer(SnapshotSerializer):
"""
Serializes Python objects to amber format with comprehensive type handling.
Supports:
- Built-in types (dict, list, tuple, set, etc.)
- Custom objects with __repr__ or __dict__
- Named tuples and dataclasses
- Complex nested structures
"""
@staticmethod
def object_as_named_tuple(obj: Any) -> Any:
"""
Convert object to named tuple representation bypassing custom __repr__.
Parameters:
- obj: Object to convert
Returns:
Any: Named tuple representation of object
"""Usage examples:
def test_amber_default(snapshot):
# Uses AmberSnapshotExtension by default
data = {
"users": [
{"name": "Alice", "scores": [85, 92]},
{"name": "Bob", "scores": [78, 91]}
],
"metadata": {"version": "1.0", "timestamp": "2023-12-01"}
}
assert data == snapshot
def test_custom_repr_bypass(snapshot):
from syrupy.extensions.amber.serializer import AmberDataSerializer
class CustomClass:
def __init__(self, value):
self.value = value
def __repr__(self):
return "CustomClass(...)" # Hides internal state
obj = CustomClass("important_data")
# Bypass custom __repr__ to show actual structure
assert AmberDataSerializer.object_as_named_tuple(obj) == snapshotExtensions that create one file per test case, suitable for binary data and individual snapshots.
class SingleFileSnapshotExtension(AbstractSyrupyExtension):
"""
Base class for single-file extensions creating one .raw file per test.
Features:
- One file per test case
- Binary and text mode support
- Raw data storage without additional formatting
"""
class WriteMode(Enum):
BINARY = "b"
TEXT = "t"
class SingleFileAmberSnapshotExtension(SingleFileSnapshotExtension):
"""
Amber extension variant creating one file per snapshot.
Combines amber serialization with single-file storage.
"""Usage examples:
def test_single_file_raw(snapshot):
from syrupy.extensions.single_file import SingleFileSnapshotExtension
# Binary data stored as-is in .raw file
binary_data = b"\\x00\\x01\\x02\\x03"
assert binary_data == snapshot.use_extension(SingleFileSnapshotExtension)
def test_single_file_amber(snapshot):
from syrupy.extensions.single_file import SingleFileAmberSnapshotExtension
# Amber serialization but one file per test
data = {"config": {"debug": True, "port": 8080}}
assert data == snapshot.use_extension(SingleFileAmberSnapshotExtension)Specialized extensions for image data supporting PNG and SVG formats.
class PNGImageSnapshotExtension(SingleFileSnapshotExtension):
"""
Extension for PNG image snapshots creating .png files.
Expects byte string input representing PNG image data.
"""
class SVGImageSnapshotExtension(SingleFileSnapshotExtension):
"""
Extension for SVG image snapshots creating .svg files.
Expects string input containing SVG markup.
"""Usage examples:
def test_png_image(snapshot):
from syrupy.extensions.image import PNGImageSnapshotExtension
# PNG image as bytes (from PIL, matplotlib, etc.)
png_bytes = create_chart_image() # Returns bytes
assert png_bytes == snapshot.use_extension(PNGImageSnapshotExtension)
def test_svg_image(snapshot):
from syrupy.extensions.image import SVGImageSnapshotExtension
# SVG as string markup
svg_content = '''
<svg width="100" height="100">
<circle cx="50" cy="50" r="25" fill="blue"/>
</svg>
'''
assert svg_content == snapshot.use_extension(SVGImageSnapshotExtension)Extension for clean JSON output ideal for API responses and structured data.
class JSONSnapshotExtension(AbstractSyrupyExtension):
"""
Extension creating .json files with proper JSON formatting.
Features:
- Clean, properly indented JSON output
- Support for dictionaries and lists
- Custom serialization for non-JSON types
- Human-readable formatting
"""Usage examples:
def test_api_response(snapshot):
from syrupy.extensions.json import JSONSnapshotExtension
# API response data
response = {
"status": "success",
"data": {
"user": {"id": 123, "name": "Alice"},
"permissions": ["read", "write"]
},
"meta": {"timestamp": "2023-12-01T10:00:00Z"}
}
assert response == snapshot.use_extension(JSONSnapshotExtension)
def test_list_data(snapshot):
from syrupy.extensions.json import JSONSnapshotExtension
# List of structured data
users = [
{"id": 1, "name": "Alice", "active": True},
{"id": 2, "name": "Bob", "active": False}
]
assert users == snapshot.use_extension(JSONSnapshotExtension)Global configuration for default extension class used across all snapshots.
DEFAULT_EXTENSION = AmberSnapshotExtensionUsage with CLI:
# Change default extension class
pytest --snapshot-default-extension=syrupy.extensions.json.JSONSnapshotExtension
# Update snapshots with custom extension
pytest --snapshot-update --snapshot-default-extension=syrupy.extensions.json.JSONSnapshotExtensionTo create custom extensions, inherit from AbstractSyrupyExtension and implement required methods:
from syrupy.extensions.base import AbstractSyrupyExtension
class MyCustomExtension(AbstractSyrupyExtension):
def serialize(self, data, **kwargs):
# Custom serialization logic
return str(data)
def get_file_extension(self):
return "custom"
def read_snapshot(self, snapshot_location, snapshot_name):
# Custom read logic
pass
def write_snapshot(self, snapshot_data, snapshot_location, snapshot_name):
# Custom write logic
pass
# Usage in tests
def test_with_custom_extension(snapshot):
assert my_data == snapshot.use_extension(MyCustomExtension)Install with Tessl CLI
npx tessl i tessl/pypi-syrupyevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10