Skip to main content

pytest-recording

codecov Build Version Python versions License

A pytest plugin powered by VCR.py to record and replay HTTP traffic.

Features

  • Straightforward pytest.mark.vcr, that reflects VCR.use_cassettes API;
  • Combining multiple VCR cassettes;
  • Network access blocking;
  • The rewrite recording mode that rewrites cassettes from scratch.

Installation

This project can be installed via pip:

pip install pytest-recording

Usage

import pytest
import requests


# cassettes/{module_name}/test_single.yaml will be used
@pytest.mark.vcr
def test_single():
    assert requests.get("http://httpbin.org/get").text == '{"get": true}'


# cassettes/{module_name}/example.yaml will be used
@pytest.mark.default_cassette("example.yaml")
@pytest.mark.vcr
def test_default():
    assert requests.get("http://httpbin.org/get").text == '{"get": true}'


# these cassettes will be used in addition to the default one
@pytest.mark.vcr("/path/to/ip.yaml", "/path/to/get.yaml")
def test_multiple():
    assert requests.get("http://httpbin.org/get").text == '{"get": true}'
    assert requests.get("http://httpbin.org/ip").text == '{"ip": true}'


# Make assertions based on the cassette calls/responses:
@pytest.mark.vcr
def test_call_count(vcr):
    assert requests.get("http://httpbin.org/get").text == '{"get": true}'
    assert requests.get("http://httpbin.org/ip").text == '{"ip": true}'
    # See https://vcrpy.readthedocs.io/en/latest/advanced.html for more info
    # about the Cassette object:
    assert vcr.play_count == 2

Run your tests:

pytest --record-mode=once test_network.py

Default recording mode

pytest-recording uses the none VCR recording mode by default to prevent unintentional network requests. To allow them you need to pass a different recording mode (e.g. once) via the --record-mode CLI option to your test command. See more information about available recording modes in the official VCR documentation

Configuration

You can provide the recording configuration with the vcr_config fixture, which could be any scope - session, package, module, or function. It should return a dictionary that will be passed directly to VCR.use_cassettes under the hood.

import pytest


@pytest.fixture(scope="module")
def vcr_config():
    return {"filter_headers": ["authorization"]}

For more granular control you need to pass these keyword arguments to individual pytest.mark.vcr marks, and in this case all arguments will be merged into a single dictionary with the following priority (low -> high):

  • vcr_config fixture
  • all marks from the most broad scope ("session") to the most narrow one ("function")

Example:

import pytest

pytestmark = [pytest.mark.vcr(ignore_localhost=True)]


@pytest.fixture(scope="module")
def vcr_config():
    return {"filter_headers": ["authorization"]}


@pytest.mark.vcr(filter_headers=[])
def test_one(): ...


@pytest.mark.vcr(filter_query_parameters=["api_key"])
def test_two(): ...

Resulting VCR configs for each test:

  • test_one - {"ignore_localhost": True, "filter_headers": []}
  • test_two - {"ignore_localhost": True, "filter_headers": ["authorization"], "filter_query_parameters": ["api_key"]}

The vcr_config fixture forwards any keyword argument to vcrpy, so ignore_hosts and other VCR.py options work directly:

import pytest


@pytest.fixture(scope="module")
def vcr_config():
    return {
        "filter_headers": ["authorization"],
        "ignore_hosts": ["169.254.169.254", "metadata.google.internal"],
    }

You can get access to the used VCR instance via pytest_recording_configure hook. It might be useful for registering custom matchers, persisters, etc.:

# conftest.py


def jurassic_matcher(r1, r2):
    assert r1.uri == r2.uri and "JURASSIC PARK" in r1.body, "required string (JURASSIC PARK) not found in request body"


def pytest_recording_configure(config, vcr):
    vcr.register_matcher("jurassic", jurassic_matcher)

You can disable the VCR.py integration entirely by passing the --disable-recording CLI option.

Cassettes directory

Each test module stores its cassettes in cassettes/{module_name}/ next to the module. To use a different directory, override the vcr_cassette_dir fixture:

# conftest.py
import os
import pytest


@pytest.fixture
def vcr_cassette_dir():
    return os.path.join(os.path.dirname(__file__), "shared_cassettes")

Relative paths in pytest.mark.vcr("shared.yaml") resolve against this directory. The cassette_library_dir key in vcr_config does not affect extra cassettes.

Rewrite record mode

It is possible to rewrite a cassette from scratch and not extend it with new entries as it works now with the all record mode from VCR.py.

However, it will rewrite only the default cassette and won't touch extra cassettes.

import pytest


@pytest.fixture(scope="module")
def vcr_config():
    return {"record_mode": "rewrite"}

Or via command-line option:

pytest --record-mode=rewrite tests/

Blocking network access

To have more confidence that your tests will not go over the wire, you can block it with pytest.mark.block_network mark:

import pytest
import requests

@pytest.mark.block_network
def test_multiple():
    assert requests.get("http://httpbin.org/get").text == '{"get": true}'

...
# in case of access
RuntimeError: Network is disabled for host '...'

Besides marks, the network access could be blocked globally with --block-network command-line option.

However, if VCR.py recording is enabled, the network is not blocked for tests with pytest.mark.vcr.

Example:

import pytest
import requests


@pytest.mark.vcr
def test_multiple():
    assert requests.get("http://httpbin.org/get").text == '{"get": true}'

Run pytest:

pytest --record-mode=once --block-network tests/

The network blocking feature supports socket-based transports and pycurl.

It is possible to allow access to specified hosts during network blocking:

import pytest
import requests


@pytest.mark.block_network(allowed_hosts=["127.0.0.1"])
def test_access():
    assert requests.get("http://127.0.0.1:8000/").status_code == 200
    with pytest.raises(RuntimeError, match=r"^Network is disabled for host '192.0.2.1'"):
        requests.get("http://192.0.2.1")

Entries in allowed_hosts are regular expressions matched against the host the client connects to. Socket-based clients like requests resolve hostnames before connecting, so list IP addresses for them (127.0.0.1, 10.0.0.*). pycurl exposes the hostname, so hostname patterns like httpbin.* work there.

Or via command-line option:

pytest --record-mode=once --block-network --allowed-hosts=127.0.0.1,::1 tests/

Or via vcr_config fixture:

import pytest


@pytest.fixture(autouse=True)
def vcr_config():
    return {"allowed_hosts": ["127.0.0.1"]}

To allow every private IP address (loopback, LAN, Docker networks), use allow_private_ips. It helps when services run in Docker Compose and their IPs are not known in advance:

import pytest


@pytest.mark.block_network(allow_private_ips=True)
def test_service(): ...

The same works with --allow-private-ips on the command line or {"allow_private_ips": True} in vcr_config. "Private" follows Python's ipaddress module, so it includes the 169.254.169.254 cloud metadata endpoint. pycurl passes hostnames to the guard, so only URLs with IP addresses match there.

pytest_recording.network.blocking_context blocks both socket and pycurl. To block only one of them, pass transports:

from pytest_recording.network import blocking_context

with blocking_context(transports=("socket",)):
    # Socket connections are blocked; pycurl is unchanged.
    ...

network.block and network.unblock accept the same argument. The block_network mark and --block-network always block both.

Additional resources

Looking for more examples? Check out this article about pytest-recording.

Contributing

To run the tests:

tox -p all

For more information, take a look at our contributing guide.

Test Isolation for Package Maintainers

When running pytest-based tests in a packaging environment, globally installed plugins can break test suites by injecting unexpected hooks or fixtures (e.g. pytest-pretty) that your code isn’t designed for.

To guarantee a clean, reproducible test run:

export PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
export PYTEST_PLUGINS=pytest_httpbin.plugin,pytest_mock,pytest_recording.plugin
  • PYTEST_DISABLE_PLUGIN_AUTOLOAD Disables loading of any plugins via setuptools entry-points; only those you explicitly list will be activated.
  • PYTEST_PLUGINS Comma-separated list of plugin modules pytest should load (the core plugin manager still discovers builtin plugins and conftest.py).

Include these exports in your package’s build or CI script so that system-wide pytest plugins (e.g. linting, formatting, or unrelated test helpers) cannot interfere with your tests.

Python support

Pytest-recording supports:

  • CPython 3.10, 3.11, 3.12, 3.13, 3.14 and 3.15
  • PyPy 7.3 (3.11)

License

The code in this project is licensed under MIT license. By contributing to pytest-recording, you agree that your contributions will be licensed under its MIT license.

Metadata

Release files for pytest-recording 0.15.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-recording 0.15.0
File Size Uploaded Provenance Actions
pytest_recording-0.15.0.tar.gz 22.8 kB With provenance Details for pytest_recording-0.15.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pytest-recording 0.15.0
File Interpreter ABI Platform Actions
pytest_recording-0.15.0-py3-none-any.whl Python 3 none any Details for pytest_recording-0.15.0-py3-none-any.whl

Total release size: 38.6 kB

Release files / pytest_recording-0.15.0.tar.gz

Download URL pytest_recording-0.15.0.tar.gz
Size 22.8 kB
Tags Source
SHA-256 checksum
How to use checksums
af96135d60cad55836192d8e2e4186d00b4712035ead2116790956282c5b0feb
BLAKE2b-256 checksum
How to use checksums
4233d15bcc3da7bd0d0e04122d67204f0fa8d5c8e4167fd86e45645240176a59
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 15, 2026.

Transparency log

Release files / pytest_recording-0.15.0-py3-none-any.whl

Download URL pytest_recording-0.15.0-py3-none-any.whl
Size 15.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
837de20090e5c043d1377f402eed075ad03d825ffe033f3b5ab95f53b57ece9f
BLAKE2b-256 checksum
How to use checksums
48efedf7f21aae7af4374d95ca158bdc2dff6e6df0670f169821613908807c31
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 15, 2026.

Transparency log

Security for pytest-recording 0.15.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pytest_recording-0.15.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 15, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page