VCR.py records HTTP responses in cassettes and replays them later. Integration tests become fast and deterministic, but recorded files may contain headers, tokens, and personal data.

import requests
import vcr

recorder = vcr.VCR(
    record_mode="once",
    filter_headers=["authorization"],
    filter_query_parameters=["api_key"],
)


@recorder.use_cassette("tests/cassettes/status.yaml")
def test_status() -> None:
    response = requests.get("https://api.example.com/status", timeout=5)
    assert response.status_code == 200

once records a missing cassette and then requires a match. Review YAML before committing and filter cookies, bodies, and identifiers as required. Never capture production responses without authorization.

Use focused mocks for unit tests and VCR.py for integration boundaries. For HTTPX see RESPX; for requests review the Python requests guide.

The official VCR.py documentation, accessed July 22, 2026, covers matchers, filters, and record modes. Establish a cassette refresh policy and retain controlled live contract tests.

Choose a record mode on purpose

once is the usual default for CI: create the cassette locally, commit it, and fail if a new interaction appears. none never records and only replays, which is safer when you want to guarantee offline runs. new_episodes appends unseen interactions and can hide drift if you are not reviewing diffs carefully. all rewrites every time and is a poor fit for shared suites.

Record against a staging or sandbox API with synthetic credentials. Do not point a recording session at production customer data. When the remote contract changes, delete or regenerate the affected cassette deliberately instead of editing YAML by hand unless the change is a trivial filter fix.

Filter secrets before the cassette exists

Filters run before persistence. Configure filter_headers, filter_query_parameters, and filter_post_data_parameters for tokens, API keys, session cookies, and passwords. For bodies that embed secrets in JSON, use a custom before_record callback that redacts fields by name.

def scrub_body(request):
    if request.body and b"password" in request.body:
        request.body = b'{"password":"<FILTERED>"}'
    return request


recorder = vcr.VCR(
    record_mode="once",
    filter_headers=["authorization", "cookie"],
    before_record_request=scrub_body,
)

Review the cassette in the pull request as carefully as application code. A leaked bearer token in YAML is still a secret. Prefer placeholder values that keep the structure readable for debugging.

Match the request that matters

By default VCR.py matches method and URI. Add matchers when the same endpoint is called with different bodies or headers that change the response, such as body, headers, or selected custom matchers. Over-matching makes cassettes brittle; under-matching can replay the wrong interaction.

Normalize volatile parts of the URI when timestamps or random IDs appear in paths. Either stabilize the client under test or teach the matcher to ignore those segments. Assert on domain outcomes in the test, not on every header recorded in the cassette.

Organize cassettes like fixtures

Keep one cassette per scenario with a descriptive path, for example tests/cassettes/payments/create_success.yaml. Sharing a large cassette across unrelated tests makes refreshes painful and reviews noisy. Name files after the behavior under test, not after the HTTP client library.

Commit cassettes with the suite when CI must run without network access. Document who regenerates them and how often. A quarterly refresh plus an on-demand refresh after known API changes is usually enough. Pair cassette tests with a small live contract suite that runs on a schedule or behind an explicit marker.

When VCR.py is the wrong tool

Unit tests that only need a fixed JSON body are often clearer with a transport mock or a stubbed client interface. VCR.py shines when URL construction, headers, redirects, and real response shapes all matter together. It is weaker when every run must mutate remote state that cannot be replayed safely.

Async HTTPX code may need an adapter or a different mocking library. Confirm library support before adopting VCR.py as the only integration strategy. Keep timeouts in the client under test so a misconfigured live recording fails fast instead of hanging the suite.

Operational checklist

Before merging, confirm filters remove secrets, the record mode cannot accidentally hit the network in CI, cassette diffs were reviewed, and a refresh plan exists. Run the suite offline at least once. Keep a separate, rare live check for the remote contract.

VCR.py turns a real HTTP conversation into a reproducible fixture. Treat that fixture as sensitive configuration: filtered, versioned, refreshed on purpose, and never mistaken for a complete substitute for contract testing.