CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-vcrpy

Automatically mock your HTTP interactions to simplify and speed up testing

Overview
Eval results
Files

test-integration.mddocs/

Test Framework Integration

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.

Capabilities

VCRMixin Class

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
        """

VCRTestCase Class

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.
    """
    pass

Usage Examples

Basic VCRTestCase Usage

import 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()

Custom VCR Configuration with Mixin

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)

Conditional VCR Usage

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)

Per-Test Cassette Configuration

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
        pass

Advanced Mixin Customization

import 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)

Integration with Test Discovery

# 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)

Directory Structure

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
        └── ...

Best Practices

Cassette Organization

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"

Error Handling in Tests

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

docs

core-configuration.md

data-filtering.md

error-handling.md

index.md

record-modes.md

request-matching.md

request-response.md

serialization.md

test-integration.md

tile.json