VCR for subprocess - record and replay subprocess calls for testing
Project description
Subprocess VCR
A Video Cassette Recorder (VCR) for subprocess commands that dramatically speeds up test execution by recording and replaying subprocess calls.
Quick Start
import subprocess
import pytest
# Mark test to use VCR - that's it!
@pytest.mark.subprocess_vcr
def test_with_vcr():
result = subprocess.run(["echo", "hello"], capture_output=True, text=True)
assert result.stdout == "hello\n"
Run tests:
# Record new interactions, replay existing ones
pytest --subprocess-vcr=record
# Replay only - fails if subprocess call not in cassette (for CI)
pytest --subprocess-vcr=replay
Recording Modes
Subprocess VCR supports several recording modes:
-
record- Replays existing recordings, records new ones. For each subprocess call, it first checks if a recording exists. If found, it replays that recording. If not found, it executes and records the new subprocess call. Useful for incremental test development. -
replay- Replay only. Fails if a subprocess call is not found in the cassette. Ensures deterministic test execution in CI. -
reset- Always record, replacing any existing cassettes and their metadata. Use this to refresh all recordings or when library behavior has changed. -
replay+reset- Attempts to replay from existing cassettes, but on any test failure or missing recording, automatically retries the ENTIRE test in reset mode. This has the benefit overresetof only resetting the cassette where necessary: when replay succeeds, the existing cassette and metadata are preserved. -
disable- No VCR, subprocess calls execute normally (default).
Filters for Normalization and Redaction
Subprocess VCR provides a powerful filter system to normalize dynamic values and redact sensitive information in your recordings. This ensures cassettes are portable, secure, and deterministic.
Built-in Filters
PathFilter
Normalizes filesystem paths that change between test runs, including paths relative to the current working directory:
from subprocess_vcr.filters import PathFilter
# Default normalization (pytest paths, home dirs, CWD, etc.)
@pytest.mark.subprocess_vcr(filters=[PathFilter()])
def test_with_paths():
# Pytest temp paths
subprocess.run(["ls", "/tmp/pytest-of-user/pytest-123/test_dir"])
# Recorded as: ["ls", "<TMP>/test_dir"]
# Current working directory paths
subprocess.run(["cat", "/home/user/project/data/file.txt"], cwd="/home/user/project")
# Recorded as: ["cat", "<CWD>/data/file.txt"] with cwd: "<CWD>"
# Custom path replacements
filter = PathFilter(replacements={
r"/opt/myapp": "<APP_ROOT>",
r"/var/log/\w+": "<LOG_DIR>",
})
RedactFilter
Removes sensitive information:
from subprocess_vcr.filters import RedactFilter
# Redact by patterns
filter = RedactFilter(
patterns=[r"api_key=\w+", r"Bearer \w+"],
env_vars=["API_KEY", "DATABASE_URL"],
)
@pytest.mark.subprocess_vcr(filters=[filter])
def test_with_secrets():
subprocess.run(["curl", "-H", "Authorization: Bearer abc123"])
# Recorded as: ["curl", "-H", "Authorization: <REDACTED>"]
Combining Filters
Using Multiple Filters
Filters are applied in order:
@pytest.mark.subprocess_vcr(filters=[
PathFilter(), # Handles all path normalization including CWD
RedactFilter(env_vars=["API_KEY", "DATABASE_URL"]),
])
def test_complex_command():
subprocess.run(["docker", "build", "-t", "myapp:latest", "."])
Global Configuration
Set filters for all tests in conftest.py:
@pytest.fixture(scope="session")
def subprocess_vcr_config():
return {
"filters": [
PathFilter(), # Handles all path normalization
RedactFilter(env_vars=["API_KEY"]),
]
}
Creating Custom Filters
Inherit from BaseFilter:
from subprocess_vcr.filters import BaseFilter
class MyCustomFilter(BaseFilter):
def before_record(self, interaction: dict) -> dict:
"""Modify interaction before saving to cassette."""
# Example: normalize custom IDs in output
if interaction.get("stdout"):
interaction["stdout"] = re.sub(
r"request-id: \w+",
"request-id: <REQUEST_ID>",
interaction["stdout"]
)
return interaction
def before_playback(self, interaction: dict) -> dict:
"""Modify interaction when loading from cassette."""
# Usually just return unchanged
return interaction
# Use the custom filter
@pytest.mark.subprocess_vcr(filters=[MyCustomFilter()])
def test_with_custom_filter():
subprocess.run(["myapp", "process"])
VCR Context in Test Reports
When tests fail while using subprocess VCR, pytest shows additional context in the test report to help with debugging:
----------------------------- subprocess-vcr -----------------------------
This test replayed subprocess calls from VCR cassette: test_example.yaml
To re-record this test, run with: --subprocess-vcr=reset
This context appears for ANY test failure when VCR is replaying, helping you understand whether the failure might be due to outdated recordings.
Example Cassette
version: "1.0"
interactions:
- args:
- echo
- hello world
kwargs:
stdout: PIPE
stderr: PIPE
text: true
duration: 0.005
returncode: 0
stdout: |
hello world
stderr: ""
pid: 12345
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file subprocess_vcr-0.1.0.tar.gz.
File metadata
- Download URL: subprocess_vcr-0.1.0.tar.gz
- Upload date:
- Size: 57.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.8.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
323771e8755ce67ec889c31e12b5a1b7892c7ad97397b4cae0752a6db2ab3317
|
|
| MD5 |
851a368fddec5ff5025f9c9a7e35e714
|
|
| BLAKE2b-256 |
1ca0796b850f9fb6bb8c148130887f4dde565fa717f39686c706fdef55ffd515
|
File details
Details for the file subprocess_vcr-0.1.0-py3-none-any.whl.
File metadata
- Download URL: subprocess_vcr-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.8.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c4c2e663a7cd8866b54aa4a267205e8b4f5554d1f08604ec8834b18eda5ae86a
|
|
| MD5 |
94c04a98108500f0ccc9c4bc90146e79
|
|
| BLAKE2b-256 |
aacb3bddbdb1cfb89dd346a42fe073d6c53f69fdbaa88fa0125d26000810c373
|