0
# Test Framework Integration
1
2
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.
3
4
## Capabilities
5
6
### VCRMixin Class
7
8
Base mixin class that adds VCR functionality to existing test cases.
9
10
```python { .api }
11
class VCRMixin:
12
"""
13
A TestCase mixin that provides VCR integration.
14
15
Automatically manages cassette lifecycle for test methods,
16
setting up cassettes in setUp() and cleaning up in teardown.
17
"""
18
19
vcr_enabled: bool = True
20
21
def setUp(self) -> None:
22
"""
23
Set up VCR cassette for the test method.
24
25
Creates and enters cassette context manager, scheduling cleanup
26
for test teardown. Calls super().setUp() to maintain inheritance chain.
27
"""
28
29
def _get_vcr(self, **kwargs) -> VCR:
30
"""
31
Get VCR instance with configuration.
32
33
Args:
34
**kwargs: VCR configuration overrides
35
36
Returns:
37
VCR: Configured VCR instance
38
"""
39
40
def _get_vcr_kwargs(self, **kwargs) -> dict:
41
"""
42
Get VCR configuration parameters for this test.
43
44
Args:
45
**kwargs: Additional configuration parameters
46
47
Returns:
48
dict: VCR configuration dictionary
49
"""
50
51
def _get_cassette_library_dir(self) -> str:
52
"""
53
Get directory path for storing cassette files.
54
55
Returns:
56
str: Path to cassettes directory (default: ./cassettes relative to test file)
57
"""
58
59
def _get_cassette_name(self) -> str:
60
"""
61
Generate cassette filename for current test method.
62
63
Returns:
64
str: Cassette filename in format ClassName.method_name.yaml
65
"""
66
```
67
68
### VCRTestCase Class
69
70
Ready-to-use TestCase class with VCR integration.
71
72
```python { .api }
73
class VCRTestCase(VCRMixin, unittest.TestCase):
74
"""
75
Complete TestCase class with VCR integration.
76
77
Inherits from both VCRMixin and unittest.TestCase,
78
providing full test functionality with automatic VCR cassette management.
79
"""
80
pass
81
```
82
83
## Usage Examples
84
85
### Basic VCRTestCase Usage
86
87
```python
88
import unittest
89
import requests
90
from vcr.unittest import VCRTestCase
91
92
class TestAPIIntegration(VCRTestCase):
93
"""Test class with automatic VCR integration."""
94
95
def test_get_user_data(self):
96
"""Test API call - cassette auto-generated as TestAPIIntegration.test_get_user_data.yaml"""
97
response = requests.get('https://jsonplaceholder.typicode.com/users/1')
98
self.assertEqual(response.status_code, 200)
99
data = response.json()
100
self.assertIn('name', data)
101
102
def test_create_post(self):
103
"""Another test method with its own cassette."""
104
response = requests.post(
105
'https://jsonplaceholder.typicode.com/posts',
106
json={'title': 'Test Post', 'body': 'Test content', 'userId': 1}
107
)
108
self.assertEqual(response.status_code, 201)
109
110
if __name__ == '__main__':
111
unittest.main()
112
```
113
114
### Custom VCR Configuration with Mixin
115
116
```python
117
import unittest
118
import requests
119
import vcr
120
from vcr.unittest import VCRMixin
121
122
class TestWithCustomVCR(VCRMixin, unittest.TestCase):
123
"""Test class with custom VCR configuration."""
124
125
def _get_vcr_kwargs(self, **kwargs):
126
"""Override to provide custom VCR configuration."""
127
return {
128
'record_mode': vcr.mode.NEW_EPISODES,
129
'filter_headers': ['authorization', 'user-agent'],
130
'filter_query_parameters': ['api_key'],
131
'serializer': 'json',
132
'decode_compressed_response': True,
133
**kwargs # Allow per-test overrides
134
}
135
136
def _get_cassette_library_dir(self):
137
"""Store cassettes in custom directory."""
138
return 'tests/fixtures/vcr_cassettes'
139
140
def test_authenticated_request(self):
141
"""Test with authentication that gets filtered."""
142
response = requests.get(
143
'https://api.example.com/protected',
144
headers={'Authorization': 'Bearer secret-token'}
145
)
146
self.assertEqual(response.status_code, 200)
147
```
148
149
### Conditional VCR Usage
150
151
```python
152
import os
153
import unittest
154
from vcr.unittest import VCRMixin
155
156
class TestConditionalVCR(VCRMixin, unittest.TestCase):
157
"""Test class with conditional VCR usage."""
158
159
@property
160
def vcr_enabled(self):
161
"""Enable VCR only in certain environments."""
162
return os.getenv('USE_VCR', 'true').lower() == 'true'
163
164
def test_external_api(self):
165
"""Test that works with or without VCR."""
166
if self.vcr_enabled:
167
# When VCR is enabled, uses recorded responses
168
response = requests.get('https://httpbin.org/json')
169
else:
170
# When VCR is disabled, makes real HTTP requests
171
response = requests.get('https://httpbin.org/json')
172
173
self.assertEqual(response.status_code, 200)
174
```
175
176
### Per-Test Cassette Configuration
177
178
```python
179
from vcr.unittest import VCRTestCase
180
import vcr
181
182
class TestPerTestConfig(VCRTestCase):
183
"""Test class with per-test cassette configuration."""
184
185
def _get_vcr_kwargs(self, **kwargs):
186
"""Base configuration for all tests."""
187
return {
188
'filter_headers': ['user-agent'],
189
'record_mode': vcr.mode.ONCE,
190
**kwargs
191
}
192
193
def test_normal_recording(self):
194
"""Uses default configuration."""
195
response = requests.get('https://httpbin.org/get')
196
self.assertEqual(response.status_code, 200)
197
198
def test_new_episodes_mode(self):
199
"""Override record mode for this test."""
200
# This would need to be handled through setUp override or similar
201
pass
202
```
203
204
### Advanced Mixin Customization
205
206
```python
207
import unittest
208
import requests
209
import vcr
210
from vcr.unittest import VCRMixin
211
212
class APITestMixin(VCRMixin):
213
"""Custom mixin with additional API testing utilities."""
214
215
API_BASE_URL = 'https://api.example.com'
216
217
def _get_vcr_kwargs(self, **kwargs):
218
"""Standard API testing VCR configuration."""
219
return {
220
'record_mode': vcr.mode.NEW_EPISODES,
221
'filter_headers': ['authorization', 'x-api-key'],
222
'filter_query_parameters': ['api_key', 'timestamp'],
223
'match_on': ['method', 'scheme', 'host', 'port', 'path', 'query'],
224
'serializer': 'json',
225
**kwargs
226
}
227
228
def _get_cassette_library_dir(self):
229
"""Store cassettes organized by test module."""
230
import os
231
test_dir = os.path.dirname(__file__)
232
return os.path.join(test_dir, 'cassettes', self.__class__.__name__)
233
234
def api_request(self, method, endpoint, **kwargs):
235
"""Helper method for making API requests."""
236
url = f"{self.API_BASE_URL}/{endpoint.lstrip('/')}"
237
return requests.request(method, url, **kwargs)
238
239
def setUp(self):
240
"""Extended setup with API-specific initialization."""
241
super().setUp()
242
# Additional setup for API tests
243
self.headers = {'User-Agent': 'TestSuite/1.0'}
244
245
class TestUserAPI(APITestMixin, unittest.TestCase):
246
"""Test user-related API endpoints."""
247
248
def test_get_user_profile(self):
249
"""Test retrieving user profile."""
250
response = self.api_request('GET', '/users/123', headers=self.headers)
251
self.assertEqual(response.status_code, 200)
252
253
def test_update_user_profile(self):
254
"""Test updating user profile."""
255
update_data = {'name': 'Updated Name'}
256
response = self.api_request('PUT', '/users/123', json=update_data, headers=self.headers)
257
self.assertEqual(response.status_code, 200)
258
```
259
260
### Integration with Test Discovery
261
262
```python
263
# test_api.py
264
import unittest
265
from vcr.unittest import VCRTestCase
266
267
class TestPublicAPI(VCRTestCase):
268
"""Tests for public API endpoints."""
269
270
def test_health_check(self):
271
response = requests.get('https://api.example.com/health')
272
self.assertEqual(response.status_code, 200)
273
274
# test_suite.py
275
import unittest
276
from test_api import TestPublicAPI
277
278
def create_test_suite():
279
"""Create test suite with VCR-enabled tests."""
280
suite = unittest.TestSuite()
281
282
# Add VCR-enabled test classes
283
suite.addTest(unittest.makeSuite(TestPublicAPI))
284
285
return suite
286
287
if __name__ == '__main__':
288
runner = unittest.TextTestRunner(verbosity=2)
289
suite = create_test_suite()
290
runner.run(suite)
291
```
292
293
## Directory Structure
294
295
When using VCR unittest integration, the typical directory structure looks like:
296
297
```
298
tests/
299
├── test_api.py # Test file
300
├── cassettes/ # Default cassette directory
301
│ ├── TestAPIIntegration.test_get_user_data.yaml
302
│ ├── TestAPIIntegration.test_create_post.yaml
303
│ └── TestWithCustomVCR.test_authenticated_request.json
304
└── fixtures/
305
└── vcr_cassettes/ # Custom cassette directory
306
├── TestCustomAPI.test_method.yaml
307
└── ...
308
```
309
310
## Best Practices
311
312
### Cassette Organization
313
314
```python
315
class OrganizedTestCase(VCRTestCase):
316
"""Well-organized test case with clear cassette structure."""
317
318
def _get_cassette_library_dir(self):
319
"""Organize cassettes by test class."""
320
import os
321
return os.path.join(
322
os.path.dirname(__file__),
323
'cassettes',
324
self.__class__.__name__
325
)
326
327
def _get_cassette_name(self):
328
"""Include test category in cassette names."""
329
category = getattr(self, 'test_category', 'general')
330
return f"{category}.{self._testMethodName}.yaml"
331
```
332
333
### Error Handling in Tests
334
335
```python
336
class RobustTestCase(VCRTestCase):
337
"""Test case with robust error handling."""
338
339
def setUp(self):
340
"""Setup with error handling."""
341
try:
342
super().setUp()
343
except Exception as e:
344
self.skipTest(f"VCR setup failed: {e}")
345
346
def test_with_fallback(self):
347
"""Test with fallback behavior."""
348
try:
349
response = requests.get('https://api.example.com/data')
350
self.assertEqual(response.status_code, 200)
351
except requests.RequestException as e:
352
if hasattr(self, 'cassette') and self.cassette:
353
# VCR should have prevented this
354
self.fail(f"Unexpected network error with VCR: {e}")
355
else:
356
# No VCR or VCR failed - expected in some environments
357
self.skipTest(f"Network request failed without VCR: {e}")
358
```