Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Vcr Plugin Laravel Package

php-http/vcr-plugin

A PHP-HTTP plugin that records real HTTP interactions and replays them in tests, similar to Ruby's VCR. Drop it into any HTTPlug-compatible client to capture responses once, then run fast, deterministic, offline test suites without hitting external APIs.

Deep Wiki
Context7

Getting Started

php-http/vcr-plugin is a dev-only HTTP decorator: it wraps an existing HTTPlug/PSR-18 client, records real responses to disk, and replays them on later runs. It is not a full mocking framework — it sits below your application code, at the transport layer, so it works with SDKs and clients you don’t control.

Install and verify:

composer require --dev php-http/vcr-plugin

You also need a concrete client to wrap — Guzzle 7, Symfony HttpClient via psr18-adapter, or an HTTPlug client. The package is a decorator, so there is no standalone Client you can use without one.

The smallest useful setup:

use Http\Client\Vcr\Vcr;
use Http\Client\Vcr\Storage\JsonStorage;
use GuzzleHttp\Client as Guzzle;

$vcr = Vcr::create(
    new JsonStorage(__DIR__ . '/fixtures/http'),
    Vcr::MODE_RECORD
);

$client = $vcr->wrap(new Guzzle(['base_uri' => 'https://api.example.com']));

$response = $client->sendRequest(new \GuzzleHttp\Psr7\Request('GET', '/users/1'));

First use case: a feature test that hits a third-party API. Run once with recording on, inspect the JSON fixtures under tests/fixtures/http/, scrub anything sensitive, commit them, then switch the mode to replay for CI. From that point the test has no network dependency.

Look first at vendor/php-http/vcr-plugin/README.md and the Vcr class: constructor/factory signature and mode constants have moved between releases, so confirm against the installed version rather than trusting blog posts.

Implementation Patterns

Wrap once, per test. Build the client in a base test case or trait so every test gets an isolated VCR instance and a fixture directory scoped to the test class:

protected function vcrClient(string $suite): ClientInterface
{
    $vcr = Vcr::create(
        new JsonStorage(__DIR__ . "/fixtures/{$suite}"),
        $this->mode()   // record locally, replay in CI
    );

    return $vcr->wrap(new Guzzle(['base_uri' => config('services.api.base_uri')]));
}

private function mode(): string
{
    return getenv('VCR_RECORD') ? Vcr::MODE_RECORD : Vcr::MODE_REPLAY;
}

Mode strategy. Record locally (VCR_RECORD=1), replay everywhere else, and reserve passthrough for explicitly allow-listed hosts (health checks, localhost). Treat replay-miss as a test failure — a missing fixture should never silently hit the network in CI.

Laravel binding. Keep production code on an interface (App\Contracts\ApiClient) and swap the implementation in a test service provider or directly in setUp():

$this->app->instance(ApiClient::class, new VcrBackedApiClient($this->vcrClient('billing')));

This is the cleanest fit when an SDK builds its own Guzzle client and you can’t intercept Http::fake().

Choosing VCR over Http::fake(). Use Http::fake() for code that goes through the Http facade — it’s simpler, faster, and has request assertions. Reach for VCR when (a) you’re testing a third-party SDK that constructs its own transport, (b) you want the real response payload shape (pagination envelopes, error bodies) in your fixtures, or (c) you want one recorded fixture reused across unit and integration tests.

Fixture organization. One directory per test class, one file per interaction, named deterministically. Avoid recording whole paginated flows; record the two or three interactions a test actually exercises so fixtures stay reviewable in diffs.

Scrubbing on record. Centralize redaction — replace bearer tokens, cookies, API keys and PII with stable placeholders before the storage layer writes the fixture, and inject matching placeholder values into requests during replay. Keeping redaction in one place (a normalizer or a thin storage decorator) prevents “it worked until someone rotated a token” failures.

Custom storage. If JSON-on-disk doesn’t fit (e.g. fixtures need encryption, or you want in-memory recordings for one-off tests), implement the package’s Storage interface and pass it to Vcr::create(). This is the intended extension point.

Parallel test runs. Pest/PHPUnit parallel mode will collide on a shared fixture directory. Include the test name and getmypid() (or a per-worker env var) in the storage path, or run the VCR suite serially.

Gotchas and Tips

Fixtures are sensitive by default. Every recorded request and response is stored verbatim — Authorization headers, cookies, session IDs, personal data, internal hostnames. Scrub at record time, add the fixture directory to your secret-scanning rules, and review the first recording of any new suite manually before committing.

Volatile request data breaks replay matching. The package fingerprints requests (method + URI + selected headers + body). Tokens, timestamps, User-Agent, Accept-Encoding, X-Request-Id and trace headers change between runs and produce a replay miss that looks like a network failure. Configure header filtering/normalization so only semantically meaningful headers participate in matching.

Canonicalize before hashing. Query-string ordering, JSON key ordering, body whitespace, and trailing slashes all affect the fingerprint. Normalize both the recording and the replay path identically, or you’ll get sporadic misses that pass locally and fail in CI.

Redirects and retries multiply interactions. A 302 chain or a retrying middleware records one interaction per hop/attempt. Replay needs all of them in the same order; a fixture set recorded with retries enabled is often unusable when retries are disabled. Record with retries off, or record the exact configuration the test uses.

Streaming, multipart, and binary bodies are fragile. Body hashing is unstable for streamed uploads and boundary-randomized multipart requests. Don’t VCR these unless you can pin the body representation; test them against a stub server instead.

Time-dependent responses. Tokens with short TTLs and payloads containing now make fixtures expire or subtly change. Freeze time in tests, or accept that those specific interactions need re-recording periodically.

Debugging a replay miss. The error usually surfaces as a client exception, not “fixture not found.” Log the request method/URI and the computed fingerprint, then confirm: the storage path is correct, the mode constant is actually replay, and the fingerprint matches a fixture you can see on disk. A typo in the fixture directory is the most common cause.

Make recording hard to trigger in CI. Require an explicit env var for record mode and fail hard if it’s set in CI. A silently re-recording pipeline will commit fresh credentials and mask upstream API changes for months.

Version pinning. With a small star count and recent releases, pin the minor version, vendor-review upgrades, and re-check the entry-point signature (Vcr::create() vs. constructor) and mode constant names against vendor/ rather than the README on GitHub — they have diverged across releases.

Extension points worth knowing. Custom Storage, header/body normalizers, and wrapping the VCR inside a PluginClient chain for logging or retry middleware. If you need per-host behavior (record one host, replay another), compose two wrapped clients rather than trying to configure one client with conditional logic.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin