Skip to main content

A pytest plugin for snapshot verification with optional visual diff viewer.

Project description

pytest-verify

PyPI version Python Versions License Downloads Build Status

pytest-verify is a snapshot testing plugin for pytest that ensures your test outputs remain consistent across runs.

It automatically saves and compares snapshots of your test results and can launch a visual diff viewer for reviewing differences directly in your terminal.


Installation

Basic installation:

pip install pytest-verify

Usage Overview

Any pytest test that returns a value can be decorated with @verify_snapshot.

  • On the first run, pytest-verify creates baseline snapshots.
  • On subsequent runs, it compares the new output with the expected snapshot.
  • If differences are detected, a diff is displayed:

screenshot

🗨⚠️ Test FAILED, pytest-verify will ask whether to replace the expected.

💡 How This Works ?

from pytest_verify import verify_snapshot


@verify_snapshot(
    ignore_fields=[
        "$.user.profile.updated_at",
        "$.devices[*].debug",
        "$.sessions[*].events[*].meta.trace",
    ],
    abs_tol=0.05,
    rel_tol=0.02,
)
def test_ignore_multiple_fields():
    """
      - Exact path:            $.user.profile.updated_at
      - Array wildcard:        $.devices[*].debug
      - Deep nested wildcard:  $.sessions[*].events[*].meta.trace
    """
    return {
        "user": {
            "id": 7,
            "profile": {"updated_at": "2025-10-14T10:00:00Z", "age": 30},
        },
        "devices": [
            {"id": "d1", "debug": "alpha", "temp": 20.0},
            {"id": "d2", "debug": "beta", "temp": 20.1},
        ],
        "sessions": [
            {"events": [{"meta": {"trace": "abc"}, "value": 10.0}]},
            {"events": [{"meta": {"trace": "def"}, "value": 10.5}]},
        ],
    }

The decorator @verify_snapshot automatically performs the following steps:

  1. Format Detection
    Detects that the return value is a JSON snapshot (because the test returns a Python dict).

  2. Serialization
    Serializes the result into a canonical, pretty-formatted JSON string.

  3. Comparison
    Compares the serialized result against the existing .expected.json snapshot file.

  4. Baseline Creation
    On the first test run, if no snapshot exists, a baseline file is created:

    ::

    __snapshots__/test_ignore_fields_complex.expected.json
    
  5. Subsequent Runs
    On later runs, the result is compared to the existing snapshot. If differences occur outside the ignored fields or tolerance limits, a unified diff is displayed in the terminal.

Examples

from pytest_verify import verify_snapshot

@verify_snapshot()
def test_text_snapshot():
    return "Hello, pytest-verify!"

Passes when: - The returned text is identical to the saved snapshot. - Whitespace at the start or end of the string is ignored.

Fails when: - The text content changes (e.g. "Hello, pytest!").


from pytest_verify import verify_snapshot
# ignore fields
@verify_snapshot(ignore_fields=["id"])
def test_ignore_fields():
    return {"id": 2, "name": "Mohamed"}

Passes when: - Ignored fields differ (id), but all other keys match.

Fails when: - Non-ignored fields differ (e.g. "name").


# numeric tolerance
@verify_snapshot(abs_tol=1e-3; rel_tol=1e-3)
def test_with_tolerance():
    return {"value": 3.1416}

Passes when: - Numeric values differ slightly within tolerance (abs_tol=0.001).

Fails when: - The numeric difference exceeds the allowed tolerance.


# abs tol fields in json
@verify_snapshot(abs_tol_fields = {"$.network.*.v": 1.0})
def test_abs_tol_fields():
    return '{"network": {"n1": {"v": 10}, "n2": {"v": 10}}}'

Passes when: - Numeric values of (v) differ within tolerance.

Fails when: - The numeric difference of (v) exceeds the allowed tolerance.


# yaml ignore order
@verify_snapshot(ignore_order_yaml=False)
def test_yaml_order_sensitive():
    return """
    fruits:
      - apple
      - banana
    """

Passes when: - The order of YAML list items is identical.

Fails when: - The order changes while order sensitivity is enforced.


# yanl ignore fields
@verify_snapshot(ignore_fields=["age"])
def test_yaml_ignore_fields():
    return """
    person:
      name: Alice
      age: 31
      city: Paris
    """

Passes when: - Ignored fields (age) differ.

Fails when: - Any non-ignored fields differ.


# numeric tolerance
@verify_snapshot(abs_tol=0.02)
def test_yaml_numeric_tolerance():
    return """
    metrics:
      accuracy: 99.96
    """

Passes when: - Numeric values differ within the given absolute tolerance.

Fails when: - The difference exceeds the allowed tolerance.


@verify_snapshot(
    abs_tol_fields={"$.metrics.accuracy": 0.05},
    rel_tol_fields={"$.metrics.loss": 0.1},
    ignore_order_yaml=True
)
def test_yaml_numeric_tolerances():
    return """
    metrics:
      accuracy: 0.96
      loss: 0.105
      epoch: 10
    """
# xml numeric tolerance
@verify_snapshot(abs_tol=0.02)
def test_xml_numeric_tolerance():
    return "<metrics><score>99.96</score></metrics>"

Passes when: - Numeric differences are within tolerance.

Fails when: - Values differ by more than the allowed tolerance.


# xml numeric tolerance per field
@verify_snapshot(abs_tol_fields={"//sensor/temp": 0.5})
def test_xml_abs_tol_fields():
    return """
    <sensors>
        <sensor><temp>20.0</temp></sensor>
        <sensor><temp>21.0</temp></sensor>
    </sensors>
    """

Passes when: - Numeric values of (temp) differ within tolerance.

Fails when: - The numeric difference of (temp) exceeds the allowed tolerance.

import pandas as pd
from pytest_verify import verify_snapshot

# ignore columns
@verify_snapshot(ignore_columns=["B"])
def test_dataframe_ignore_columns():
    df = pd.DataFrame({
        "A": [1, 4],
        "B": [2, 9],   # ignored column
        "C": [3, 6],
    })
    return df

Passes when: - Ignored columns differ (B), but all other columns match.

Fails when: - Non-ignored columns differ or structure changes.


import pandas as pd
from pytest_verify import verify_snapshot

@verify_snapshot(abs_tol=0.02)
def test_dataframe_tolerance():
    df = pd.DataFrame({
        "A": [1.00, 3.00],
        "B": [2.00, 4.00],
    })
    return df

Passes when: - Numeric differences between runs are within tolerance (≤ 0.02).

Fails when: - Numeric difference exceeds tolerance (e.g. 0.0001).


import numpy as np
from pytest_verify import verify_snapshot

@verify_snapshot(abs_tol=0.01)
def test_numpy_array_tolerance():
    return np.array([[1.001, 2.0, 3.0]])

Passes when: - Element-wise numeric differences are within 0.01.

Fails when: - Differences exceed tolerance.


import numpy as np
from pytest_verify import verify_snapshot

@verify_snapshot()
def test_numpy_array_type_mismatch():
    return np.array([["1", "2", "3"]], dtype=object)

Passes when: - Element types match expected (e.g. all numeric).

Fails when: - Element types differ (e.g. numeric vs string).


import numpy as np
from pytest_verify import verify_snapshot

@verify_snapshot()
def test_numpy_array_with_none():
    return np.array([[1, None, 3]], dtype=object)

Passes when: - Missing values (None / NaN) are in the same positions.

Fails when: - Missing values occur in different positions or types differ.


Async Test Support

pytest-verify supports asynchronous (async def) test functions in addition to normal synchronous tests.

To enable this feature, you need to install the optional async extra:

pip install pytest-verify[async]

This will install pytest-asyncio as a test runner integration, allowing pytest to automatically await async tests.

Basic Example

import pytest
from pytest_verify import verify_snapshot

@pytest.mark.asyncio
@verify_snapshot()
async def test_async_snapshot():
    await asyncio.sleep(0.01)
    return {"status": "ok", "value": 42}

Automatic Async Mode

If you prefer not to use the @pytest.mark.asyncio decorator on every async test, you can configure pytest to automatically handle async functions.

Add the following to your pytest.ini file:

[pytest]
asyncio_mode = auto

With this setting, async tests work transparently without additional decorators:

from pytest_verify import verify_snapshot

@verify_snapshot()
async def test_async_auto_mode():
    return {"async": True, "auto_mode": "enabled"}

Coexistence with Sync Tests

pytest-verify handles both synchronous and asynchronous tests seamlessly.

You can mix both types within the same test suite:

@verify_snapshot()
def test_sync_snapshot():
    return {"type": "sync"}

@pytest.mark.asyncio
@verify_snapshot()
async def test_async_snapshot():
    return {"type": "async"}

Tips

  • Use ignore_fields and numeric tolerances (abs_tol, rel_tol) exactly as in sync tests.

  • If you see the message:

    You need to install a suitable plugin for your async framework

    it means that no async runner (e.g. pytest-asyncio) is installed.
    Fix it by installing the async extra:

pip install pytest-verify[async]

pytest-verify automatically detects whether a test is asynchronous and awaits it safely, ensuring consistent snapshot creation and comparison across both sync and async workflows.

Behavior Summary

Step Description
First run Creates both .expected and .actual files.
Subsequent runs Compares new output with the saved snapshot.
Match found ✅ Snapshot confirmed and updated.
Mismatch detected ⚠️ Shows diff on terminal.
Change accepted 📝 Updates expected snapshot and keeps backup.

Developer Notes

Local installation for development:

pip install -e '.[all]'

Run the test suite:

pytest -v -s

License

Licensed under the Apache License 2.0.


Author

Mohamed Tahri Email: simotahri1@gmail.com GitHub: https://github.com/metahris/pytest-verify

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_verify-1.2.0.tar.gz (24.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_verify-1.2.0-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file pytest_verify-1.2.0.tar.gz.

File metadata

  • Download URL: pytest_verify-1.2.0.tar.gz
  • Upload date:
  • Size: 24.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pytest_verify-1.2.0.tar.gz
Algorithm Hash digest
SHA256 f42de72a69ee4532116c2cb05cb5a3762567ed2887348ef1eedc8d740e7900a8
MD5 a84346ad44340622d79c18f76626a4f2
BLAKE2b-256 5599d4b55fb03d9ddf430ecefaa2bf212dcc1c548556900c0863ae8aaf72475b

See more details on using hashes here.

File details

Details for the file pytest_verify-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: pytest_verify-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 19.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pytest_verify-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bb534df91acac5427723296a0bbe5f64334cbdea0073e2a902030ada230613ba
MD5 3d5008f2d09002feacd6d4c9701fd6c2
BLAKE2b-256 373cf357a5f7b842325880934e8a783337fb4f26560f9a872a3a06fe005fa340

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page