Automatically mock your HTTP interactions to simplify and speed up testing
Integration classes for unittest framework providing automatic cassette management and VCR configuration for test cases. VCR.py provides seamless integration with Python's unittest framework through mixin classes and inheritance.
Base mixin class that adds VCR functionality to existing test cases.
class VCRMixin:
"""
A TestCase mixin that provides VCR integration.
Automatically manages cassette lifecycle for test methods,
setting up cassettes in setUp() and cleaning up in teardown.
"""
vcr_enabled: bool = True
def setUp(self) -> None:
"""
Set up VCR cassette for the test method.
Creates and enters cassette context manager, scheduling cleanup
for test teardown. Calls super().setUp() to maintain inheritance chain.
"""
def _get_vcr(self, **kwargs) -> VCR:
"""
Get VCR instance with configuration.
Args:
**kwargs: VCR configuration overrides
Returns:
VCR: Configured VCR instance
"""
def _get_vcr_kwargs(self, **kwargs) -> dict:
"""
Get VCR configuration parameters for this test.
Args:
**kwargs: Additional configuration parameters
Returns:
dict: VCR configuration dictionary
"""
def _get_cassette_library_dir(self) -> str:
"""
Get directory path for storing cassette files.
Returns:
str: Path to cassettes directory (default: ./cassettes relative to test file)
"""
def _get_cassette_name(self) -> str:
"""
Generate cassette filename for current test method.
Returns:
str: Cassette filename in format ClassName.method_name.yaml
"""Ready-to-use TestCase class with VCR integration.
class VCRTestCase(VCRMixin, unittest.TestCase):
"""
Complete TestCase class with VCR integration.
Inherits from both VCRMixin and unittest.TestCase,
providing full test functionality with automatic VCR cassette management.
"""
passimport unittest
import requests
from vcr.unittest import VCRTestCase
class TestAPIIntegration(VCRTestCase):
"""Test class with automatic VCR integration."""
def test_get_user_data(self):
"""Test API call - cassette auto-generated as TestAPIIntegration.test_get_user_data.yaml"""
response = requests.get('https://jsonplaceholder.typicode.com/users/1')
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertIn('name', data)
def test_create_post(self):
"""Another test method with its own cassette."""
response = requests.post(
'https://jsonplaceholder.typicode.com/posts',
json={'title': 'Test Post', 'body': 'Test content', 'userId': 1}
)
self.assertEqual(response.status_code, 201)
if __name__ == '__main__':
unittest.main()import unittest
import requests
import vcr
from vcr.unittest import VCRMixin
class TestWithCustomVCR(VCRMixin, unittest.TestCase):
"""Test class with custom VCR configuration."""
def _get_vcr_kwargs(self, **kwargs):
"""Override to provide custom VCR configuration."""
return {
'record_mode': vcr.mode.NEW_EPISODES,
'filter_headers': ['authorization', 'user-agent'],
'filter_query_parameters': ['api_key'],
'serializer': 'json',
'decode_compressed_response': True,
**kwargs # Allow per-test overrides
}
def _get_cassette_library_dir(self):
"""Store cassettes in custom directory."""
return 'tests/fixtures/vcr_cassettes'
def test_authenticated_request(self):
"""Test with authentication that gets filtered."""
response = requests.get(
'https://api.example.com/protected',
headers={'Authorization': 'Bearer secret-token'}
)
self.assertEqual(response.status_code, 200)import os
import unittest
from vcr.unittest import VCRMixin
class TestConditionalVCR(VCRMixin, unittest.TestCase):
"""Test class with conditional VCR usage."""
@property
def vcr_enabled(self):
"""Enable VCR only in certain environments."""
return os.getenv('USE_VCR', 'true').lower() == 'true'
def test_external_api(self):
"""Test that works with or without VCR."""
if self.vcr_enabled:
# When VCR is enabled, uses recorded responses
response = requests.get('https://httpbin.org/json')
else:
# When VCR is disabled, makes real HTTP requests
response = requests.get('https://httpbin.org/json')
self.assertEqual(response.status_code, 200)from vcr.unittest import VCRTestCase
import vcr
class TestPerTestConfig(VCRTestCase):
"""Test class with per-test cassette configuration."""
def _get_vcr_kwargs(self, **kwargs):
"""Base configuration for all tests."""
return {
'filter_headers': ['user-agent'],
'record_mode': vcr.mode.ONCE,
**kwargs
}
def test_normal_recording(self):
"""Uses default configuration."""
response = requests.get('https://httpbin.org/get')
self.assertEqual(response.status_code, 200)
def test_new_episodes_mode(self):
"""Override record mode for this test."""
# This would need to be handled through setUp override or similar
passimport unittest
import requests
import vcr
from vcr.unittest import VCRMixin
class APITestMixin(VCRMixin):
"""Custom mixin with additional API testing utilities."""
API_BASE_URL = 'https://api.example.com'
def _get_vcr_kwargs(self, **kwargs):
"""Standard API testing VCR configuration."""
return {
'record_mode': vcr.mode.NEW_EPISODES,
'filter_headers': ['authorization', 'x-api-key'],
'filter_query_parameters': ['api_key', 'timestamp'],
'match_on': ['method', 'scheme', 'host', 'port', 'path', 'query'],
'serializer': 'json',
**kwargs
}
def _get_cassette_library_dir(self):
"""Store cassettes organized by test module."""
import os
test_dir = os.path.dirname(__file__)
return os.path.join(test_dir, 'cassettes', self.__class__.__name__)
def api_request(self, method, endpoint, **kwargs):
"""Helper method for making API requests."""
url = f"{self.API_BASE_URL}/{endpoint.lstrip('/')}"
return requests.request(method, url, **kwargs)
def setUp(self):
"""Extended setup with API-specific initialization."""
super().setUp()
# Additional setup for API tests
self.headers = {'User-Agent': 'TestSuite/1.0'}
class TestUserAPI(APITestMixin, unittest.TestCase):
"""Test user-related API endpoints."""
def test_get_user_profile(self):
"""Test retrieving user profile."""
response = self.api_request('GET', '/users/123', headers=self.headers)
self.assertEqual(response.status_code, 200)
def test_update_user_profile(self):
"""Test updating user profile."""
update_data = {'name': 'Updated Name'}
response = self.api_request('PUT', '/users/123', json=update_data, headers=self.headers)
self.assertEqual(response.status_code, 200)# test_api.py
import unittest
from vcr.unittest import VCRTestCase
class TestPublicAPI(VCRTestCase):
"""Tests for public API endpoints."""
def test_health_check(self):
response = requests.get('https://api.example.com/health')
self.assertEqual(response.status_code, 200)
# test_suite.py
import unittest
from test_api import TestPublicAPI
def create_test_suite():
"""Create test suite with VCR-enabled tests."""
suite = unittest.TestSuite()
# Add VCR-enabled test classes
suite.addTest(unittest.makeSuite(TestPublicAPI))
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner(verbosity=2)
suite = create_test_suite()
runner.run(suite)When using VCR unittest integration, the typical directory structure looks like:
tests/
├── test_api.py # Test file
├── cassettes/ # Default cassette directory
│ ├── TestAPIIntegration.test_get_user_data.yaml
│ ├── TestAPIIntegration.test_create_post.yaml
│ └── TestWithCustomVCR.test_authenticated_request.json
└── fixtures/
└── vcr_cassettes/ # Custom cassette directory
├── TestCustomAPI.test_method.yaml
└── ...class OrganizedTestCase(VCRTestCase):
"""Well-organized test case with clear cassette structure."""
def _get_cassette_library_dir(self):
"""Organize cassettes by test class."""
import os
return os.path.join(
os.path.dirname(__file__),
'cassettes',
self.__class__.__name__
)
def _get_cassette_name(self):
"""Include test category in cassette names."""
category = getattr(self, 'test_category', 'general')
return f"{category}.{self._testMethodName}.yaml"class RobustTestCase(VCRTestCase):
"""Test case with robust error handling."""
def setUp(self):
"""Setup with error handling."""
try:
super().setUp()
except Exception as e:
self.skipTest(f"VCR setup failed: {e}")
def test_with_fallback(self):
"""Test with fallback behavior."""
try:
response = requests.get('https://api.example.com/data')
self.assertEqual(response.status_code, 200)
except requests.RequestException as e:
if hasattr(self, 'cassette') and self.cassette:
# VCR should have prevented this
self.fail(f"Unexpected network error with VCR: {e}")
else:
# No VCR or VCR failed - expected in some environments
self.skipTest(f"Network request failed without VCR: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-vcrpy