or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-configuration.mddata-filtering.mderror-handling.mdindex.mdrecord-modes.mdrequest-matching.mdrequest-response.mdserialization.mdtest-integration.md

test-integration.mddocs/

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

```