syrupy

repository·main·Indexed 21 days ago

https://github.com/syrupy-project/syrupy

A zero-dependency snapshot testing plugin for pytest that allows developers to assert the immutability of computed results by comparing them against stored snapshots. It features an extensible design supporting various data formats via extensions like JSONSnapshotExtension, PNGImageSnapshotExtension, and SVGImageSnapshotExtension, and provides tools for customizing serialization through matchers and filters.

Tokens
9.5K
Snippets
30
Records
48
Agent score
74%

What's inside syrupy

  1. Overview of syrupy

    main

    Syrupy is a zero-dependency pytest snapshot plugin. It is built around three core principles:

    • Extensible: Users can easily add support for new data types.
    • Idiomatic: It integrates naturally with pytest syntax, allowing for assert x == snapshot instead of specialized assertion methods.
    • Soundness: Syrupy ensures test integrity by failing the test suite if a snapshot is missing, rather than just reporting differences.
  2. How pytest-xdist interacts with snapshot detection

    main
    When using pytest-xdist for parallel execution, Syrupy supports unused snapshot detection and removal. Each worker process reports the snapshots it utilized, and the controller process aggregates these reports to identify which snapshots are truly unused across the entire test suite.
  3. Customizing Object Snapshots via Representation

    main

    Syrupy uses the object's __repr__ for serialization. You can customize how an object is snapshotted by overriding its __repr__ method.

    def __repr__(self) -> str:
        return "MyCustomClass(...)"

    To bypass a custom representation and use the standard Amber serialization, use the AmberDataSerializer.object_as_named_tuple helper.

  4. Using Syrupy in unittest.TestCase subclasses

    main

    The snapshot fixture is not directly accessible in unittest.TestCase subclasses (including Django's TestCase). You can use one of the following workarounds:

    Option 1: Using marks and a wrapper fixture Create a fixture that assigns the snapshot to the test class.

    from unittest import TestCase
    import pytest
    
    @pytest.fixture(scope="function")
    def snapshot_in_class(request, snapshot):
        request.cls.snapshot = snapshot
    
    class MyTest(TestCase):
        @pytest.mark.usefixtures("snapshot_in_class")
        def test_foo(self):
            actual = "Some computed value!"
            assert actual == self.snapshot

    Option 2: Using an autouse fixture

    from unittest import TestCase
    import pytest
    
    class MyTest(TestCase):
        @pytest.fixture(autouse=True)
        def setupSnapshot(self, snapshot):
            self.snapshot = snapshot
    
        def test_foo(self):
            actual = "Some computed value!"
            assert actual == self.snapshot
  5. Migrate from snapshottest to syrupy

    main

    Syrupy and snapshottest cannot be used together because they have conflicting arguments. To migrate, uninstall snapshottest and remove all existing snapshot directories from your project.

    pip uninstall snapshottest -y;
    find . -type d ! -path '*/\.*' -name 'snapshots' | xargs rm -r
  6. Basic Usage of Syrupy with pytest

    main

    To use Syrupy, inject the snapshot fixture into your pytest test functions. When a test fails due to a missing snapshot, run pytest with the --snapshot-update flag to generate the snapshot files. Snapshots are stored in a __snapshots__ directory adjacent to your test file; this directory should be committed to version control.

    def test_foo(snapshot):
        actual = "Some computed value!"
        assert actual == snapshot

    To update snapshots:

    pytest --snapshot-update
  7. Enable PyCharm diff viewer support

    main

    To ensure Syrupy snapshots render correctly in the PyCharm built-in diff viewer, you must apply a patch to the diff viewer library using the --snapshot-patch-pycharm-diff flag. You can automate this by adding the flag to your pytest.ini configuration file.

    [pytest]
    addopts = --snapshot-patch-pycharm-diff
  8. Use the Amber snapshot extension

    main

    The Amber extension allows syrupy to use the .ambr file format for storing snapshots. It utilizes the AmberDataSerializer to handle data serialization and deserialization. When using this extension, snapshots are stored in files with the .ambr extension.

    # Note: This extension is typically activated via syrupy configuration or plugin registration.
    # It uses the .ambr file extension for snapshot storage.
  9. How to create a full Syrupy Extension

    main

    A complete Syrupy extension that handles serialization, storage, reporting (diffing), and comparison can be created by inheriting from AbstractSyrupyExtension. This class combines SnapshotSerializer, SnapshotCollectionStorage, SnapshotReporter, and SnapshotComparator.

    By implementing AbstractSyrupyExtension, you provide a unified interface for Syrupy to manage the entire lifecycle of a snapshot, from converting the data to finding it on disk and displaying diffs when tests fail.

    from syrupy.extensions.base import AbstractSyrupyExtension
    
    class MyFullExtension(AbstractSyrupyExtension):
        # Implement all required methods from the parent ABCs
        pass
  10. How SnapshotReporter generates diffs

    main

    The SnapshotReporter class is responsible for generating human-readable diffs between serialized_data (the data received during the test) and snapshot_data (the data stored on disk).

    It uses diff_snapshots to produce a string containing the diff. The reporter handles:

    • Line-by-line comparison: Using diff_lines to iterate through changes.
    • Context management: Using _context_line_count to show surrounding lines for context, using SYMBOL_ELLIPSIS (...) to indicate truncated sections.
    • End-of-line markers: Optionally showing special characters for newlines (SYMBOL_NEW_LINE) or carriage returns (SYMBOL_CARRIAGE).
    • Styling: Applying terminal styles (via snapshot_style, received_style, etc.) to highlight additions, removals, and changes.