php-vcr/phpunit-testlistener-vcr
PHPUnit TestListener integrating PHP-VCR via @vcr annotations. Automatically turns VCR on/off per test and records/replays HTTP interactions using named cassettes. Install with Composer and register the listener in phpunit.xml.
Install the Package Add the package via Composer:
composer require --dev php-vcr/phpunit-testlistener-vcr
Ensure php-vcr/php-vcr is also installed (required dependency).
Configure PHP-VCR
Create a php-vcr.yml (or php-vcr.php) in your project root:
# php-vcr.yml
storage: ./tests/vcr_cassettes
record_if: env(RECORD) == 'true'
Enable the Listener
Add the listener to your phpunit.xml:
<phpunit>
<extensions>
<extension class="PHPVCR\PHPUnit\TestListenerVCR"/>
</extensions>
</phpunit>
First Use Case: Record a Test
Run tests with RECORD=true to generate cassettes:
RECORD=true ./vendor/bin/phpunit
Subsequent runs will replay recorded responses.
Record Initial Requests Write a test for an API endpoint, then record the response:
// tests/Feature/UserTest.php
public function test_fetch_user()
{
$response = $this->get('/api/user/1');
$response->assertStatus(200);
}
Run with RECORD=true to save the cassette.
Update Cassettes When Needed
Use RECORD=update to modify cassettes for changed responses:
RECORD=update ./vendor/bin/phpunit tests/Feature/UserTest
Conditional Recording
Use record_if in php-vcr.yml to control when to record:
record_if: env(RECORD) == 'true' || env(GITHUB_ACTIONS) == 'true'
HTTP Client Mocking
Replace Http::fake() with VCR for realistic HTTP interactions:
use PHPVCR\VCR;
public function test_external_payment()
{
VCR::turnOn();
$response = Http::post('https://payment-gateway.com/charge', [...]);
$response->assertOk();
}
Database + API Tests Combine with Laravel’s database transactions:
public function test_order_flow()
{
$this->actingAs($user);
VCR::turnOn();
$response = $this->post('/orders', [...]);
$response->assertCreated();
}
VCR::turnOn() per test class or method to isolate cassettes:
class PaymentTest extends TestCase
{
protected function setUp(): void
{
VCR::turnOn();
}
}
Cassette Naming Collisions
{classname}_{methodname}.yaml.php-vcr.yml:
cassette_namer: 'tests/vcr_cassettes/%s/%s.yaml'
Dynamic Data Leaking
ignore_header or ignore_body in php-vcr.yml:
ignore_header:
- Date
- Authorization
Slow Tests on CI
record_if: env(RECORD) == 'true' || env(LOCAL) == 'true'
Laravel’s HTTP Client Quirks
Http::baseUrl() is consistent across environments.Http::baseUrl('https://staging.example.com');
Verify Cassettes
Check recorded cassettes in ./tests/vcr_cassettes/ for correctness.
Use VCR::debug() to log interactions:
VCR::debug(function ($interaction) {
dump($interaction->getUri(), $interaction->getBody());
});
Reset Cassettes Delete the cassette directory to force re-recording:
rm -rf tests/vcr_cassettes/*
Custom Matchers
Extend PHPVCR\VCR\Matcher\MatcherInterface to ignore dynamic fields:
class IgnoreTimestampMatcher implements MatcherInterface
{
public function matches($expected, $actual) { ... }
}
Register in php-vcr.yml:
matchers:
- PHPVCR\VCR\Matcher\IgnoreTimestampMatcher
Pre/Post-Request Hooks
Use VCR::beforeRecord() and VCR::afterRecord() for setup/teardown:
VCR::beforeRecord(function ($interaction) {
$interaction->setBody(json_encode([
'user_id' => auth()->id(),
]));
});
Environment-Specific Config
Load different php-vcr.yml files per environment:
# php-vcr.local.yml
storage: ./tests/vcr_cassettes/local
How can I help you explore Laravel packages today?