{"id":37075182,"url":"https://github.com/metahris/pytest-verify","last_synced_at":"2026-01-14T08:50:50.463Z","repository":{"id":318724966,"uuid":"1073161576","full_name":"metahris/pytest-verify","owner":"metahris","description":"a snapshot testing plugin for pytest that ensures your test outputs remain consistent across runs.","archived":false,"fork":false,"pushed_at":"2025-10-25T16:02:04.000Z","size":1280,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-11-27T12:56:26.915Z","etag":null,"topics":["compare","dataclasses","dataframe","json","numpy-arrays","plugin","pydantic-v2","pytest","snapshot-testing","text","xml","yaml"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/metahris.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-09T17:48:38.000Z","updated_at":"2025-11-14T17:46:45.000Z","dependencies_parsed_at":"2025-10-12T20:12:13.067Z","dependency_job_id":"c389ccc0-70e6-4bfc-a011-930b56b1e3f6","html_url":"https://github.com/metahris/pytest-verify","commit_stats":null,"previous_names":["metahris/pytest-verify"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/metahris/pytest-verify","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metahris%2Fpytest-verify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metahris%2Fpytest-verify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metahris%2Fpytest-verify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metahris%2Fpytest-verify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/metahris","download_url":"https://codeload.github.com/metahris/pytest-verify/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metahris%2Fpytest-verify/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28414698,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:38:59.149Z","status":"ssl_error","status_checked_at":"2026-01-14T08:38:43.588Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compare","dataclasses","dataframe","json","numpy-arrays","plugin","pydantic-v2","pytest","snapshot-testing","text","xml","yaml"],"created_at":"2026-01-14T08:50:48.881Z","updated_at":"2026-01-14T08:50:50.395Z","avatar_url":"https://github.com/metahris.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pytest-verify\n[![PyPI version](https://badge.fury.io/py/pytest-verify.svg)](https://pypi.org/project/pytest-verify/)\n[![Python Versions](https://img.shields.io/pypi/pyversions/pytest-verify.svg)](https://pypi.org/project/pytest-verify/)\n[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)\n[![Downloads](https://static.pepy.tech/badge/pytest-verify)](https://pepy.tech/project/pytest-verify)\n[![Build Status](https://github.com/metahris/pytest-verify/actions/workflows/run_tests.yml/badge.svg)](https://github.com/metahris/pytest-verify/actions)\n\n**pytest-verify** is a snapshot testing plugin for **pytest** that\nensures your test outputs remain consistent across runs.\n\nIt automatically saves and compares snapshots of your test results and\ncan launch a **visual diff viewer** for reviewing differences\ndirectly in your terminal.\n\n---\n\n## Installation\n\nBasic installation:\n\n    pip install pytest-verify\n\n---\n\n## Usage Overview\n\nAny pytest test that **returns a value** can be decorated with\n`@verify_snapshot`.\n\n- On the **first run**, pytest-verify creates baseline snapshots.\n- On **subsequent runs**, it compares the new output with the expected\n  snapshot.\n- If differences are detected, a diff is displayed:\n\n![screenshot](docs/images/test_simple_json_failed.png)\n\n🗨⚠️  Test FAILED, pytest-verify will ask whether to replace the expected.\n\n💡 How This Works ?\n===================\n\n``` python\nfrom pytest_verify import verify_snapshot\n\n\n@verify_snapshot(\n    ignore_fields=[\n        \"$.user.profile.updated_at\",\n        \"$.devices[*].debug\",\n        \"$.sessions[*].events[*].meta.trace\",\n    ],\n    abs_tol=0.05,\n    rel_tol=0.02,\n)\ndef test_ignore_multiple_fields():\n    \"\"\"\n      - Exact path:            $.user.profile.updated_at\n      - Array wildcard:        $.devices[*].debug\n      - Deep nested wildcard:  $.sessions[*].events[*].meta.trace\n    \"\"\"\n    return {\n        \"user\": {\n            \"id\": 7,\n            \"profile\": {\"updated_at\": \"2025-10-14T10:00:00Z\", \"age\": 30},\n        },\n        \"devices\": [\n            {\"id\": \"d1\", \"debug\": \"alpha\", \"temp\": 20.0},\n            {\"id\": \"d2\", \"debug\": \"beta\", \"temp\": 20.1},\n        ],\n        \"sessions\": [\n            {\"events\": [{\"meta\": {\"trace\": \"abc\"}, \"value\": 10.0}]},\n            {\"events\": [{\"meta\": {\"trace\": \"def\"}, \"value\": 10.5}]},\n        ],\n    }\n```\n\n\nThe decorator ``@verify_snapshot`` automatically performs the following steps:\n\n1. **Format Detection**  \n   Detects that the return value is a JSON snapshot (because the test returns a Python ``dict``).\n\n2. **Serialization**  \n   Serializes the result into a canonical, pretty-formatted JSON string.\n\n3. **Comparison**  \n   Compares the serialized result against the existing ``.expected.json`` snapshot file.\n\n\n5. **Baseline Creation**  \n   On the first test run, if no snapshot exists, a baseline file is created:\n\n   ::\n\n       __snapshots__/test_ignore_fields_complex.expected.json\n\n6. **Subsequent Runs**  \n   On later runs, the result is compared to the existing snapshot.\n   If differences occur outside the ignored fields or tolerance limits,\n   a unified diff is displayed in the terminal.\n\n\n## Examples\n\n``` python\nfrom pytest_verify import verify_snapshot\n\n@verify_snapshot()\ndef test_text_snapshot():\n    return \"Hello, pytest-verify!\"\n```\n\n**Passes when:** - The returned text is identical to the saved\nsnapshot. - Whitespace at the start or end of the string is ignored.\n\n**Fails when:** - The text content changes (e.g. `\"Hello, pytest!\"`).\n\n---\n\n``` python\nfrom pytest_verify import verify_snapshot\n# ignore fields\n@verify_snapshot(ignore_fields=[\"id\"])\ndef test_ignore_fields():\n    return {\"id\": 2, \"name\": \"Mohamed\"}\n```\n\n**Passes when:** - Ignored fields differ (`id`), but all other keys\nmatch.\n\n**Fails when:** - Non-ignored fields differ (e.g. `\"name\"`).\n\n---\n\n``` python\n# numeric tolerance\n@verify_snapshot(abs_tol=1e-3; rel_tol=1e-3)\ndef test_with_tolerance():\n    return {\"value\": 3.1416}\n```\n\n**Passes when:** - Numeric values differ slightly within tolerance\n(`abs_tol=0.001`).\n\n**Fails when:** - The numeric difference exceeds the allowed tolerance.\n\n---\n\n``` python\n# abs tol fields in json\n@verify_snapshot(abs_tol_fields = {\"$.network.*.v\": 1.0})\ndef test_abs_tol_fields():\n    return '{\"network\": {\"n1\": {\"v\": 10}, \"n2\": {\"v\": 10}}}'\n```\n\n**Passes when:** - Numeric values of (`v`) differ within tolerance.\n\n**Fails when:** - The numeric difference of (`v`) exceeds the allowed tolerance.\n\n---\n\n``` python\n# yaml ignore order\n@verify_snapshot(ignore_order_yaml=False)\ndef test_yaml_order_sensitive():\n    return \"\"\"\n    fruits:\n      - apple\n      - banana\n    \"\"\"\n```\n\n**Passes when:** - The order of YAML list items is identical.\n\n**Fails when:** - The order changes while order sensitivity is enforced.\n\n---\n\n``` python\n# yanl ignore fields\n@verify_snapshot(ignore_fields=[\"age\"])\ndef test_yaml_ignore_fields():\n    return \"\"\"\n    person:\n      name: Alice\n      age: 31\n      city: Paris\n    \"\"\"\n```\n\n**Passes when:** - Ignored fields (`age`) differ.\n\n**Fails when:** - Any non-ignored fields differ.\n\n---\n\n``` python\n# numeric tolerance\n@verify_snapshot(abs_tol=0.02)\ndef test_yaml_numeric_tolerance():\n    return \"\"\"\n    metrics:\n      accuracy: 99.96\n    \"\"\"\n```\n\n**Passes when:** - Numeric values differ within the given absolute\ntolerance.\n\n**Fails when:** - The difference exceeds the allowed tolerance.\n\n---\n\n``` python\n@verify_snapshot(\n    abs_tol_fields={\"$.metrics.accuracy\": 0.05},\n    rel_tol_fields={\"$.metrics.loss\": 0.1},\n    ignore_order_yaml=True\n)\ndef test_yaml_numeric_tolerances():\n    return \"\"\"\n    metrics:\n      accuracy: 0.96\n      loss: 0.105\n      epoch: 10\n    \"\"\"\n```\n\n\n``` python\n# xml numeric tolerance\n@verify_snapshot(abs_tol=0.02)\ndef test_xml_numeric_tolerance():\n    return \"\u003cmetrics\u003e\u003cscore\u003e99.96\u003c/score\u003e\u003c/metrics\u003e\"\n```\n\n**Passes when:** - Numeric differences are within tolerance.\n\n**Fails when:** - Values differ by more than the allowed tolerance.\n\n---\n\n``` python\n# xml numeric tolerance per field\n@verify_snapshot(abs_tol_fields={\"//sensor/temp\": 0.5})\ndef test_xml_abs_tol_fields():\n    return \"\"\"\n    \u003csensors\u003e\n        \u003csensor\u003e\u003ctemp\u003e20.0\u003c/temp\u003e\u003c/sensor\u003e\n        \u003csensor\u003e\u003ctemp\u003e21.0\u003c/temp\u003e\u003c/sensor\u003e\n    \u003c/sensors\u003e\n    \"\"\"\n```\n\n**Passes when:** - Numeric values of (`temp`) differ within tolerance.\n\n**Fails when:** - The numeric difference of (`temp`) exceeds the allowed tolerance.\n\n\n``` python\nimport pandas as pd\nfrom pytest_verify import verify_snapshot\n\n# ignore columns\n@verify_snapshot(ignore_columns=[\"B\"])\ndef test_dataframe_ignore_columns():\n    df = pd.DataFrame({\n        \"A\": [1, 4],\n        \"B\": [2, 9],   # ignored column\n        \"C\": [3, 6],\n    })\n    return df\n```\n\n**Passes when:** - Ignored columns differ (`B`), but all other columns\nmatch.\n\n**Fails when:** - Non-ignored columns differ or structure changes.\n\n---\n\n``` python\nimport pandas as pd\nfrom pytest_verify import verify_snapshot\n\n@verify_snapshot(abs_tol=0.02)\ndef test_dataframe_tolerance():\n    df = pd.DataFrame({\n        \"A\": [1.00, 3.00],\n        \"B\": [2.00, 4.00],\n    })\n    return df\n```\n\n**Passes when:** - Numeric differences between runs are within tolerance\n(≤ 0.02).\n\n**Fails when:** - Numeric difference exceeds tolerance (e.g. 0.0001).\n\n---\n\n``` python\nimport numpy as np\nfrom pytest_verify import verify_snapshot\n\n@verify_snapshot(abs_tol=0.01)\ndef test_numpy_array_tolerance():\n    return np.array([[1.001, 2.0, 3.0]])\n```\n\n**Passes when:** - Element-wise numeric differences are within 0.01.\n\n**Fails when:** - Differences exceed tolerance.\n\n---\n\n``` python\nimport numpy as np\nfrom pytest_verify import verify_snapshot\n\n@verify_snapshot()\ndef test_numpy_array_type_mismatch():\n    return np.array([[\"1\", \"2\", \"3\"]], dtype=object)\n```\n\n**Passes when:** - Element types match expected (e.g. all numeric).\n\n**Fails when:** - Element types differ (e.g. numeric vs string).\n\n---\n\n``` python\nimport numpy as np\nfrom pytest_verify import verify_snapshot\n\n@verify_snapshot()\ndef test_numpy_array_with_none():\n    return np.array([[1, None, 3]], dtype=object)\n```\n\n**Passes when:** - Missing values (\u003cspan class=\"title-ref\"\u003eNone\u003c/span\u003e /\n\u003cspan class=\"title-ref\"\u003eNaN\u003c/span\u003e) are in the same positions.\n\n**Fails when:** - Missing values occur in different positions or types\ndiffer.\n\n---\n\n## Async Test Support\n\n``pytest-verify`` supports asynchronous (``async def``) test functions in addition to normal synchronous tests.\n\nTo enable this feature, you need to install the optional ``async extra``:\n\n    pip install pytest-verify[async]\n\nThis will install ``pytest-asyncio`` as a test runner integration, allowing ``pytest`` to automatically await async tests.\n\n### Basic Example\n\n``` python\n\nimport pytest\nfrom pytest_verify import verify_snapshot\n\n@pytest.mark.asyncio\n@verify_snapshot()\nasync def test_async_snapshot():\n    await asyncio.sleep(0.01)\n    return {\"status\": \"ok\", \"value\": 42}\n```\n\n### Automatic Async Mode\n\nIf you prefer not to use the ``@pytest.mark.asyncio`` decorator on every async test,\nyou can configure pytest to automatically handle async functions.\n\nAdd the following to your ``pytest.ini`` file:\n``` ini\n[pytest]\nasyncio_mode = auto\n```\nWith this setting, async tests work transparently without additional decorators:\n``` python\n\nfrom pytest_verify import verify_snapshot\n\n@verify_snapshot()\nasync def test_async_auto_mode():\n    return {\"async\": True, \"auto_mode\": \"enabled\"}\n``` \nCoexistence with Sync Tests\n---------------------------\n\n``pytest-verify`` handles both synchronous and asynchronous tests seamlessly.\n\nYou can mix both types within the same test suite:\n\n``` python\n\n@verify_snapshot()\ndef test_sync_snapshot():\n    return {\"type\": \"sync\"}\n\n@pytest.mark.asyncio\n@verify_snapshot()\nasync def test_async_snapshot():\n    return {\"type\": \"async\"}\n``` \nTips\n----\n\n* Use ``ignore_fields`` and numeric tolerances (``abs_tol``, ``rel_tol``) exactly as in sync tests.\n* If you see the message:\n\n  ``You need to install a suitable plugin for your async framework``\n\n  it means that no async runner (e.g. ``pytest-asyncio``) is installed.  \n  Fix it by installing the async extra:\n\n```bash\npip install pytest-verify[async]\n```\n``pytest-verify`` automatically detects whether a test is asynchronous and awaits it safely,\nensuring consistent snapshot creation and comparison across both sync and async workflows.\n\n\n## Behavior Summary\n\n\u003ctable style=\"width:99%;\"\u003e\n\u003ccolgroup\u003e\n\u003ccol style=\"width: 31%\" /\u003e\n\u003ccol style=\"width: 65%\" /\u003e\n\u003ccol style=\"width: 1%\" /\u003e\n\u003c/colgroup\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eStep\u003c/th\u003e\n\u003cth\u003eDescription\u003c/th\u003e\n\u003cth\u003e\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eFirst run\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eCreates both \u003ccode\u003e.expected\u003c/code\u003e and \u003ccode\u003e.actual\u003c/code\u003e\nfiles.\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eSubsequent runs\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003eCompares new output with the saved snapshot.\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eMatch found\u003c/strong\u003e\u003c/td\u003e\n\u003ctd colspan=\"2\"\u003e✅ Snapshot confirmed and updated.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eMismatch detected\u003c/strong\u003e\u003c/td\u003e\n\u003ctd\u003e⚠️ Shows diff on terminal.\u003c/td\u003e\n\u003ctd\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003cstrong\u003eChange accepted\u003c/strong\u003e\u003c/td\u003e\n\u003ctd colspan=\"2\"\u003e📝 Updates expected snapshot and keeps backup.\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\n---\n\n## Developer Notes\n\nLocal installation for development:\n\n    pip install -e '.[all]'\n\nRun the test suite:\n\n    pytest -v -s\n\n---\n\n## License\n\nLicensed under the **Apache License 2.0**.\n\n---\n\n## Author\n\n**Mohamed Tahri** Email: `simotahri1@gmail.com` GitHub:\n\u003chttps://github.com/metahris/pytest-verify\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetahris%2Fpytest-verify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmetahris%2Fpytest-verify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetahris%2Fpytest-verify/lists"}