pytest-recording
A pytest plugin powered by VCR.py to record and replay HTTP traffic.
Features
- Straightforward
pytest.mark.vcr, that reflectsVCR.use_cassettesAPI; - Combining multiple VCR cassettes;
- Network access blocking;
- The
rewriterecording 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_configfixture- 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.