Testing ยท API Mocking

VCR Recording for pytest: Record HTTP Requests and Replay Them Offline

Flaky tests caused by real network calls? Record interactions, then replay them through a local mock server.

July 11, 2026 ยท APIGhost ยท 8 min read

Network calls in tests are the number-one source of non-determinism: rate limits, timeouts, slow CI runners, and API downtime all make otherwise-correct tests fail randomly. The VCR (Video Cassette Recorder) pattern solves this: you record a real HTTP interaction once, save it to a "cassette" file, and replay that file in every subsequent test run โ€” no live network required.

This guide walks through the VCR cassette workflow in APIGhost โ€” an open-source mock server for OpenAPI specs that has first-class VCR recording built in alongside spec-driven faking.

Why VCR cassettes instead of patching?

The traditional Python approach to mocking HTTP is unittest.mock.patch or the responses library. Both work, but they require you to handcraft response bodies โ€” which drift from reality as APIs evolve and are tedious to maintain for deeply nested JSON payloads.

ApproachProsCons
unittest.mock.patchNo dependencies, familiarManual JSON; drifts from real API silently
responses libraryURL-pattern matchingStill hand-authored bodies; no serialisation to disk
vcrpyReal recordingsCoupled to underlying HTTP lib; cassettes can be large/noisy
APIGhost VCRRecorded interactions; integrates with the APIGhost mock serverRequires a running APIGhost process

APIGhost's README documents VCR recording and deterministic replay alongside spec-driven mock-server generation. Choose the workflow that fits your test suite, then review recorded data before committing it.

Installation

APIGhost is MIT-licensed. Its README documents a source install and a Homebrew installation path:

# pip โ€” documented source install
pip install git+https://github.com/Coding-Dev-Tools/apighost.git

# Homebrew (macOS / Linux)
brew tap Coding-Dev-Tools/homebrew-tap
brew install apighost

Verify the install:

apighost --version

Note: pip install apighost (bare, no --index-url) will fail โ€” the package is not on public PyPI. Use one of the commands above.

The record โ†’ replay workflow

There are three steps:

  1. Record โ€” use APIGhost's record command to save interactions to a cassette.
  2. Commit the cassette alongside your test code.
  3. Replay โ€” in CI (or locally), start APIGhost in replay mode; it serves the saved cassette without touching the network.

Step 1 โ€” Record a cassette

Point APIGhost at an OpenAPI spec and write the recorded output to a cassette path:

apighost record petstore.yaml --output fixtures/petstore-cassette.json

Use only non-sensitive test data when recording. Before committing any cassette, inspect its contents and remove credentials or personal data.

Step 2 โ€” Inspect and commit the cassette

Open fixtures/petstore-cassette.json to review what was captured before committing it. A cassette entry looks like:

{
  "id": "get_pets_001",
  "request": {
    "method": "GET",
    "path": "/v1/pets",
    "headers": {"Accept": "application/json"}
  },
  "response": {
    "status": 200,
    "headers": {"Content-Type": "application/json"},
    "body": [
      {"id": 1, "name": "Noodle", "tag": "cat"},
      {"id": 2, "name": "Biscuit"}
    ]
  },
  "schema_operation": "GET /v1/pets",
  "recorded_at": "2026-07-11T08:12:33Z"
}

Scrub any credentials or personal data from the cassette before committing it:

git add fixtures/petstore-cassette.json
git commit -m "test: add petstore VCR cassette"

Step 3 โ€” Replay in pytest

Start APIGhost in replay mode as a pytest fixture so it boots before your tests and shuts down after:

# conftest.py
import subprocess
import time
import pytest

@pytest.fixture(scope="session", autouse=True)
def apighost_replay():
    """Start APIGhost in VCR replay mode for the test session."""
    proc = subprocess.Popen(
        [
            "apighost", "replay", "fixtures/petstore-cassette.json",
            "-p", "4000",
        ],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    time.sleep(1)  # brief wait for server to bind
    yield proc
    proc.terminate()
    proc.wait()

Now point your test HTTP client at http://localhost:4000 โ€” no live network is touched:

# test_pets_api.py
import httpx

BASE = "http://localhost:4000"

def test_list_pets():
    r = httpx.get(f"{BASE}/v1/pets")
    assert r.status_code == 200
    data = r.json()
    assert isinstance(data, list)
    assert data[0]["name"] == "Noodle"   # matches our cassette

def test_get_single_pet():
    r = httpx.get(f"{BASE}/v1/pets/42")
    assert r.status_code == 200
    pet = r.json()
    assert "id" in pet

def test_create_pet():
    r = httpx.post(f"{BASE}/v1/pets", json={"name": "Pretzel", "tag": "dog"})
    assert r.status_code == 201

Run the suite:

pytest -v
# ===== 3 passed in 0.4s =====

No network calls. Deterministic. Fast.

Keeping cassettes fresh

APIs change. Here's a low-friction refresh workflow:

  1. Delete (or rename) the stale cassette file.
  2. Re-run the record step against the live API.
  3. Review the diff with git diff fixtures/petstore-cassette.json.
  4. Update assertions in your tests that depended on changed fields.
  5. Commit the new cassette.

Reviewing cassette diffs alongside test changes helps make recorded behavior explicit before you update a suite.

CI integration (GitHub Actions example)

# .github/workflows/test.yml (excerpt)
steps:
  - uses: actions/checkout@v4

  - name: Set up Python
    uses: actions/setup-python@v5
    with: {python-version: "3.12"}

  - name: Install dependencies
    run: |
      pip install git+https://github.com/Coding-Dev-Tools/apighost.git
      pip install httpx pytest

  - name: Run tests (VCR replay โ€” no live network)
    run: pytest -v
    # apighost replay starts automatically via conftest.py fixture

No secrets needed, no external API calls, no flakiness from rate limits.

Honesty note: cassettes record real response data. Never commit cassettes that contain production PII or credentials. Review the cassette file before git add.

Pricing

Free
$0
local / dev use
  • Unlimited local use
  • 100 requests/session
Pro
$12/mo
$119/yr
  • Everything in Free
  • Unlimited requests
  • VCR cassettes
  • CI/CD integration
Suite
$49/mo
discount when billed annually
  • All Coding-Dev-Tools tools under one license

Check the README for current feature and plan details before choosing a tier; the documented Free plan includes unlimited local use with a 100-request session limit.

Ready to eliminate flaky network tests? APIGhost is free to install and use locally.

View APIGhost on GitHub โ†’

Related guides


APIGhost is MIT-licensed open-source software. Claims about features and pricing are sourced from the official README (verified 2026-07-19). This site is maintained by the Coding Dev Tools team.