mrkeyoor.com_
Wed 23 Sept 00:34 UTC
PyPITestingupdated 21 Sept 2026

vcrpy review

VCR.py 8.3.0 intercepts HTTP calls made by supported Python clients, writes the real request and response to a YAML or JSON cassette, and replays matching interactions on later test runs. Version 8.3 adds niquests support, rejects recordings containing Python objects that the safe YAML loader cannot restore, provides an explicit custom-tag serializer, and fixes stale keep-alive connections crossing cassette boundaries. It is useful for realistic client integration tests, but the cassette is a stored wire conversation that can become stale or expose data if filters are incomplete.

Verdict

VCR.py 8.3.0 installed in 0.4 seconds and occupied 4 MB across 3 packages in our sandbox, with 0 pip-audit findings; use it for a small set of expensive HTTP conversations that deserve realistic replay. Choose explicit mocks when the contract is short, the cassette would hold sensitive data, or the test must detect current upstream behavior.

We installed it

Lab card: what happened when we installed vcrpyScreenshot of vcrpy documentation
Install✓ · 0.4s3 packages on disk · 4 MB
Importimport vcr in 0.71s · pure Python · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does vcrpy install cleanly?

Yes. In a fresh container with an empty cache, pip install vcrpy finished in 0.4s, leaving 3 packages and 4 MB on disk. pip-audit reported no known vulnerabilities.

What does vcrpy need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import vcr succeeded in 0.71s.

vcrpy or responses: which should you use?

responses: Your code uses requests and concise hand-authored response mocks are easier to review than recordings. VCR.py 8.3.0 installed in 0.4 seconds and occupied 4 MB across 3 packages in our sandbox, with 0 pip-audit findings; use it for a small set of expensive HTTP conversations that deserve realistic replay.

When should you not use vcrpy?

Requests or responses contain credentials, personal data, cookies, signed URLs, or tokens you cannot safely store: filters must be correct before the first recording and do not clean old cassettes

API stability4/5The `use_cassette` decorator and context manager, VCR configuration object, record modes, matchers, filters, serializers, and cassette inspection APIs remain recognizable across releases. Version 8 dropped Python 3.9 and urllib3 1.x, while 8.2 changed the HTTPX patch layer and 8.3 tightened what YAML can be recorded. The public testing model is steady, but patch-based transport support and major-version Python drops require cassette and client regression tests.
Docs4/5The official documentation explains record modes, default matching, filters, response callbacks, custom matchers, custom serializers, repeated playback, ignored hosts, cassette inspection, and unittest integration. The changelog is unusually specific about safe YAML loading and transport fixes. Security advice exists, yet it is scattered through advanced configuration; the docs could put secret scrubbing, stale recordings, and the fact that later filters do not sanitize old files much closer to the first example.
Maintenance4/5Version 8.3.0 was published on July 4, 2026, and GitHub shows an August 25, 2026 push. The repository is not archived, has 2,981 stars, and has 119 open issues after excluding pull requests. Releases in 2026 added HTTPX 2 and niquests support, repaired aiohttp compatibility, fixed connection reuse, and shipped a security correction for YAML loading. The queue is sizable because each patched client creates another compatibility surface.
Ecosystem4/5PyPI reports 6,052,272 downloads in the latest week. VCR.py covers common synchronous and asynchronous Python HTTP paths, works with unittest, and points pytest users to the separate pytest-recording plugin. YAML and JSON serializers, configurable matchers, and callbacks cover many fixture styles. Coverage is still transport-specific: subprocesses, browsers, remote services, and unsupported native libraries sit outside the patch boundary, and specialized HTTPX or requests mocks can fit smaller contracts better.

Use it if

  • You test a Python HTTP client against a slow, billed, rate-limited, or intermittently available third-party API
  • A recorded status, header set, body, redirect, or error response is more informative than a hand-written mock
  • Your team will review and commit cassette files as test fixtures, then refresh them when the upstream contract changes
  • The code uses a transport VCR.py patches, such as requests, urllib3, HTTPX, aiohttp, boto3, or niquests
Skip it if

Setup reality

Our fresh Python 3.12 install of VCR.py 8.3.0 succeeded in 0.4 seconds. It left 3 packages using 4 MB, reported 27 direct dependencies, imported as vcr in 0.71 seconds, and produced 0 known vulnerabilities in pip-audit. The distribution is pure Python, requires Python 3.10 or newer, uses the MIT license, and does not ship py.typed. PyYAML and wrapt are the ordinary runtime pieces; many HTTP clients appear only in test extras.

The first run makes a network request and writes a cassette. Under the default once mode, later matching calls replay, while a new unmatched call fails once the cassette exists. Use none in locked-down CI to forbid all new traffic, and reserve all or new_episodes for an intentional refresh with real credentials and quotas. The default matcher checks method, scheme, host, port, path, and query, but not headers or body. Add those only when they define the contract.

Scrub authorization headers, cookies, API keys in query strings, sensitive form fields, and secret response values before recording. Adding a filter tomorrow does not rewrite yesterday's cassette, so search existing fixtures separately. Version 8.3 fails before saving Python objects that its safe YAML loader cannot read. Register the new custom-tag serializer only when those objects are deliberate and every cassette source is trusted. Binary or compressed bodies can also make YAML files large and diffs hard to review.

Replay is a fixed interaction log rather than a stateful service. One recorded response is consumed once unless allow_playback_repeats is enabled, and concurrent requests can expose ordering assumptions. A transport VCR.py does not patch will still reach the network. Keep small unit tests around business decisions, use cassettes at the HTTP boundary, and schedule rerecording where upstream freshness matters. Recording may create remote data, spend quota, or trigger side effects, so treat refreshes as live integration runs.

Patterns

Record and replay one requests call record-request

import requests
import vcr

@vcr.use_cassette("tests/cassettes/user_42.yaml")
def test_fetch_user():
    response = requests.get("https://api.example.com/users/42", timeout=10)
    assert response.status_code == 200
    assert response.json()["id"] == 42

The first run can contact the real server; later runs replay only when the request matches the stored interaction.

Centralize cassette defaults configure-cassette-library

import vcr

api_vcr = vcr.VCR(
    cassette_library_dir="tests/cassettes",
    record_mode="once",
    match_on=["method", "scheme", "host", "port", "path", "query"],
)

@api_vcr.use_cassette("health.yaml")
def test_health():
    ...

These six matchers are VCR.py 8.3's defaults; headers and request bodies need explicit matchers when they affect the response.

Forbid new requests during replay block-network-in-ci

@api_vcr.use_cassette(
    "user_42.yaml",
    record_mode="none",
)
def test_user_offline():
    response = client.fetch_user(42)
    assert response.id == 42

`record_mode='none'` raises for an unmatched request and guarantees that this cassette context does not create new HTTP traffic.

Rerecord every interaction deliberately refresh-cassette

with api_vcr.use_cassette(
    "user_42.yaml",
    record_mode="all",
):
    client.fetch_user(42)

`all` sends every request to the real service and overwrites recordings, so run it only with approved credentials, quota, and test data.

Remove common secrets before writing YAML filter-secrets

secure_vcr = vcr.VCR(
    filter_headers=["authorization", "cookie", "set-cookie"],
    filter_query_parameters=["api_key", "token"],
    filter_post_data_parameters=["password"],
)

Filters apply while recording; they do not scrub cassette files that were already written or committed.

Redact a sensitive response header scrub-response

import vcr

def scrub_response(response):
    headers = response["headers"]
    headers.pop("Set-Cookie", None)
    headers.pop("set-cookie", None)
    return response

api_vcr = vcr.VCR(before_record_response=scrub_response)

Response callbacks receive cassette data before serialization; account for header casing used by the patched client.

Require the request body to match match-request-body

@api_vcr.use_cassette(
    "create_user.yaml",
    match_on=["method", "scheme", "host", "port", "path", "query", "body"],
)
def test_create_user():
    ...

Body matching catches changed payloads but also rejects timestamps, random IDs, signatures, or unstable serialization unless you normalize them first.

Compare JSON bodies by parsed value register-custom-matcher

import json
import vcr

def same_json(left, right):
    assert json.loads(left.body) == json.loads(right.body)

api_vcr = vcr.VCR()
api_vcr.register_matcher("json-body", same_json)
api_vcr.match_on = ["method", "uri", "json-body"]

A custom matcher must raise `AssertionError` with a useful message when requests differ; malformed or empty bodies need their own handling.

Replay one recorded response more than once repeat-response

with api_vcr.use_cassette(
    "feature_flag.yaml",
    allow_playback_repeats=True,
):
    assert client.flag("checkout") is True
    assert client.flag("checkout") is True

VCR.py normally consumes each matching interaction once; repeated playback can hide an accidental duplicate request, so enable it narrowly.

Assert what the test sent inspect-cassette

with api_vcr.use_cassette("search.yaml") as cassette:
    client.search("blue widgets")

assert len(cassette.requests) == 1
assert cassette.requests[0].method == "GET"
assert "q=blue+widgets" in cassette.requests[0].uri

Cassette requests appear in processing order, which makes order assertions brittle when the code starts sending work concurrently.

Replay an asynchronous HTTPX call test-async-httpx

import httpx
import vcr

@vcr.use_cassette("tests/cassettes/status.yaml")
async def test_async_status():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://api.example.com/status")
    assert response.status_code == 200

VCR.py 8.2 moved HTTPX patching to transports and added HTTPX 2 support; test against the exact HTTPX release in your environment.

Remove interactions the test no longer uses drop-unused-recordings

with api_vcr.use_cassette(
    "workflow.yaml",
    record_mode="new_episodes",
    drop_unused_requests=True,
):
    client.current_step()

`drop_unused_requests` rewrites the cassette without interactions that were not replayed, so review the resulting fixture diff before committing it.

Alternatives

PackageRegistryPick it when
responsesPyPIYour code uses requests and concise hand-authored response mocks are easier to review than recordings
requests-mockPyPIYou want a requests adapter or pytest fixture with programmatic matching
respxPyPIHTTPX route mocks and async-aware assertions matter more than recording live traffic
pytest-httpxPyPIYou want pytest-native HTTPX responses with strict checks for unused mocks

More testing guides

pytest · chai · jsdom · vitest · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.