0
# Testing Utilities
1
2
Mock objects and integration testing base classes for testing applications that use bravado. These utilities provide comprehensive testing support including response mocks, fallback result testing, and integration test fixtures for both synchronous and asynchronous HTTP clients.
3
4
## Capabilities
5
6
### Response Mocks
7
8
Mock objects that simulate bravado response behavior for unit testing.
9
10
#### IncomingResponseMock
11
12
Mock implementation of IncomingResponse for creating test responses.
13
14
```python { .api }
15
class IncomingResponseMock(IncomingResponse):
16
def __init__(self, status_code: int, **kwargs): ...
17
```
18
19
**Parameters:**
20
- `status_code` (int): HTTP status code for the mock response
21
- `**kwargs`: Additional response attributes (text, headers, etc.)
22
23
**Usage Example:**
24
25
```python
26
from bravado.testing.response_mocks import IncomingResponseMock
27
28
# Create mock response
29
mock_response = IncomingResponseMock(
30
status_code=200,
31
text='{"name": "Fluffy", "status": "available"}',
32
headers={'Content-Type': 'application/json'}
33
)
34
35
print(mock_response.status_code) # 200
36
print(mock_response.text) # {"name": "Fluffy", "status": "available"}
37
```
38
39
#### BravadoResponseMock
40
41
Mock that behaves like both HttpFuture.response() method and BravadoResponse object.
42
43
```python { .api }
44
class BravadoResponseMock:
45
def __init__(self, result, metadata=None): ...
46
def __call__(self, timeout: float = None, fallback_result=None, exceptions_to_catch: tuple = None) -> 'BravadoResponseMock': ...
47
@property
48
def result(self): ...
49
@property
50
def metadata(self) -> BravadoResponseMetadata: ...
51
```
52
53
**Parameters:**
54
- `result`: The mock result data to return
55
- `metadata`: BravadoResponseMetadata instance (optional)
56
- `timeout` (float): Timeout parameter (ignored in mock)
57
- `fallback_result`: Fallback result (ignored unless forced)
58
- `exceptions_to_catch` (tuple): Exception types (ignored in mock)
59
60
**Usage Example:**
61
62
```python
63
from bravado.testing.response_mocks import BravadoResponseMock
64
65
# Create mock response
66
mock_result = {'name': 'Fluffy', 'status': 'available'}
67
mock_response = BravadoResponseMock(mock_result)
68
69
# Use as HttpFuture.response() replacement
70
response = mock_response() # Call like future.response()
71
pet_data = response.result # Access result like normal BravadoResponse
72
73
# Or use directly as BravadoResponse
74
pet_data = mock_response.result
75
```
76
77
#### FallbackResultBravadoResponseMock
78
79
Mock that always triggers fallback result behavior, useful for testing error handling and resilience.
80
81
```python { .api }
82
class FallbackResultBravadoResponseMock:
83
def __init__(self, exception=BravadoTimeoutError(), metadata=None): ...
84
def __call__(self, timeout: float = None, fallback_result=None, exceptions_to_catch: tuple = None) -> 'FallbackResultBravadoResponseMock': ...
85
@property
86
def result(self): ...
87
@property
88
def metadata(self) -> BravadoResponseMetadata: ...
89
```
90
91
**Parameters:**
92
- `exception`: Exception to simulate (defaults to BravadoTimeoutError)
93
- `metadata`: BravadoResponseMetadata instance (optional)
94
95
**Usage Example:**
96
97
```python
98
from bravado.testing.response_mocks import FallbackResultBravadoResponseMock
99
from bravado.exception import BravadoTimeoutError
100
101
# Mock that simulates timeout and uses fallback
102
fallback_mock = FallbackResultBravadoResponseMock(
103
exception=BravadoTimeoutError("Request timeout")
104
)
105
106
# Test fallback behavior
107
fallback_data = {'name': 'Unknown', 'status': 'unavailable'}
108
response = fallback_mock(fallback_result=fallback_data)
109
110
assert response.result == fallback_data
111
assert response.metadata.is_fallback_result is True
112
```
113
114
### Integration Testing Base Classes
115
116
Comprehensive integration testing framework for testing bravado clients with real HTTP servers.
117
118
#### IntegrationTestingServicesAndClient
119
120
Pytest fixtures for setting up HTTP servers and clients for integration testing.
121
122
```python { .api }
123
class IntegrationTestingServicesAndClient:
124
@pytest.fixture(scope='session')
125
def swagger_http_server(self): ...
126
127
@pytest.fixture(scope='session')
128
def not_answering_http_server(self): ...
129
130
@pytest.fixture(params=['result', 'response'])
131
def result_getter(self, request): ...
132
```
133
134
**Fixtures:**
135
- `swagger_http_server`: Session-scoped HTTP server serving test APIs
136
- `not_answering_http_server`: Server that doesn't respond (for timeout testing)
137
- `result_getter`: Parameterized fixture for testing both .result() and .response() methods
138
139
#### IntegrationTestingFixturesMixin
140
141
Mixin class providing common fixtures and utilities for integration tests.
142
143
```python { .api }
144
class IntegrationTestingFixturesMixin:
145
http_client: HttpClient
146
http_client_type: Type[HttpClient]
147
http_future_adapter_type: Type[FutureAdapter]
148
connection_errors_exceptions: Set[Exception]
149
150
@classmethod
151
def setup_class(cls): ...
152
153
@pytest.fixture
154
def swagger_client(self) -> SwaggerClient: ...
155
156
def encode_expected_response(self, expected_response: dict) -> bytes: ...
157
```
158
159
**Class Attributes:**
160
- `http_client`: HTTP client instance for testing
161
- `http_client_type`: Type of HTTP client being tested
162
- `http_future_adapter_type`: Type of future adapter being tested
163
- `connection_errors_exceptions`: Set of connection error exception types
164
165
**Methods:**
166
- `setup_class()`: Class-level setup for test configuration
167
- `swagger_client()`: Fixture that creates SwaggerClient instances
168
- `encode_expected_response()`: Encode expected response data
169
170
#### IntegrationTestsBaseClass
171
172
Base class with comprehensive integration tests that can be inherited by HTTP client implementations.
173
174
```python { .api }
175
class IntegrationTestsBaseClass(IntegrationTestingFixturesMixin):
176
# Multiple test methods for various scenarios:
177
# - Basic API operations
178
# - Error handling (4xx, 5xx status codes)
179
# - Timeout behavior
180
# - Connection error handling
181
# - Authentication testing
182
# - Request/response validation
183
# - Fallback result behavior
184
```
185
186
This class provides a complete test suite that verifies HTTP client behavior across various scenarios.
187
188
## Usage Patterns
189
190
### Unit Testing with Mocks
191
192
```python
193
import pytest
194
from unittest.mock import patch
195
from bravado.testing.response_mocks import BravadoResponseMock
196
197
class TestPetService:
198
def setup_method(self):
199
self.pet_service = PetService() # Your service that uses bravado
200
201
@patch('your_module.swagger_client.pet.getPetById')
202
def test_get_pet_success(self, mock_get_pet):
203
# Setup mock response
204
expected_pet = {'id': 1, 'name': 'Fluffy', 'status': 'available'}
205
mock_response = BravadoResponseMock(expected_pet)
206
mock_get_pet.return_value = mock_response
207
208
# Test your service
209
pet = self.pet_service.get_pet(1)
210
211
# Assertions
212
assert pet['name'] == 'Fluffy'
213
mock_get_pet.assert_called_once_with(petId=1)
214
215
@patch('your_module.swagger_client.pet.getPetById')
216
def test_get_pet_with_fallback(self, mock_get_pet):
217
# Setup fallback mock
218
from bravado.testing.response_mocks import FallbackResultBravadoResponseMock
219
fallback_mock = FallbackResultBravadoResponseMock()
220
mock_get_pet.return_value = fallback_mock
221
222
# Test fallback behavior
223
pet = self.pet_service.get_pet_with_fallback(1)
224
225
assert pet['name'] == 'Unknown' # Your fallback data
226
assert pet['status'] == 'unavailable'
227
```
228
229
### Integration Testing with RequestsClient
230
231
```python
232
import pytest
233
from bravado.testing.integration_test import IntegrationTestsBaseClass
234
from bravado.requests_client import RequestsClient, RequestsFutureAdapter
235
236
class TestRequestsClientIntegration(IntegrationTestsBaseClass):
237
"""Integration tests for RequestsClient."""
238
239
@classmethod
240
def setup_class(cls):
241
cls.http_client_type = RequestsClient
242
cls.http_future_adapter_type = RequestsFutureAdapter
243
cls.connection_errors_exceptions = {ConnectionError}
244
super().setup_class()
245
246
def test_custom_behavior(self, swagger_client):
247
"""Test specific to RequestsClient behavior."""
248
response = swagger_client.pet.getPetById(petId=1).response()
249
assert response.result['name'] == 'doggie'
250
assert response.metadata.status_code == 200
251
```
252
253
### Integration Testing with FidoClient
254
255
```python
256
import pytest
257
from bravado.testing.integration_test import IntegrationTestsBaseClass
258
from bravado.fido_client import FidoClient, FidoFutureAdapter
259
import twisted.internet.error
260
261
class TestFidoClientIntegration(IntegrationTestsBaseClass):
262
"""Integration tests for FidoClient."""
263
264
@classmethod
265
def setup_class(cls):
266
cls.http_client_type = FidoClient
267
cls.http_future_adapter_type = FidoFutureAdapter
268
cls.connection_errors_exceptions = {
269
twisted.internet.error.ConnectionRefusedError,
270
twisted.internet.error.DNSLookupError
271
}
272
super().setup_class()
273
274
def test_async_behavior(self, swagger_client):
275
"""Test async-specific behavior."""
276
future = swagger_client.pet.getPetById(petId=1)
277
response = future.response() # This integrates with Twisted
278
assert response.result['name'] == 'doggie'
279
```
280
281
### Custom Test Server
282
283
```python
284
import bottle
285
import threading
286
import time
287
from bravado.testing.integration_test import _class_fqn
288
289
def create_test_server():
290
"""Create custom test server for your API."""
291
app = bottle.Bottle()
292
293
@app.route('/pets/<pet_id:int>', method='GET')
294
def get_pet(pet_id):
295
if pet_id == 404:
296
bottle.abort(404, "Pet not found")
297
return {
298
'id': pet_id,
299
'name': f'Pet {pet_id}',
300
'status': 'available'
301
}
302
303
@app.route('/pets', method='POST')
304
def create_pet():
305
pet_data = bottle.request.json
306
pet_data['id'] = 123 # Simulate ID assignment
307
return pet_data
308
309
return app
310
311
class TestWithCustomServer:
312
@pytest.fixture(scope='class')
313
def test_server(self):
314
"""Custom test server fixture."""
315
app = create_test_server()
316
317
# Start server in thread
318
server_thread = threading.Thread(
319
target=lambda: app.run(host='localhost', port=5000, quiet=True)
320
)
321
server_thread.daemon = True
322
server_thread.start()
323
time.sleep(0.1) # Wait for server to start
324
325
yield 'http://localhost:5000'
326
327
def test_custom_api(self, test_server):
328
from bravado.client import SwaggerClient
329
330
# You would need to provide your own swagger spec for the custom server
331
# This is just an example structure
332
spec_dict = {
333
'swagger': '2.0',
334
'info': {'title': 'Test API', 'version': '1.0'},
335
'host': 'localhost:5000',
336
'paths': {
337
'/pets/{petId}': {
338
'get': {
339
'operationId': 'getPetById',
340
'parameters': [
341
{'name': 'petId', 'in': 'path', 'type': 'integer', 'required': True}
342
],
343
'responses': {'200': {'description': 'Success'}}
344
}
345
}
346
}
347
}
348
349
client = SwaggerClient.from_spec(spec_dict, origin_url=test_server)
350
response = client.pet.getPetById(petId=1).response()
351
assert response.result['name'] == 'Pet 1'
352
```
353
354
### Testing Error Scenarios
355
356
```python
357
import pytest
358
from bravado.testing.response_mocks import FallbackResultBravadoResponseMock
359
from bravado.exception import HTTPNotFound, BravadoTimeoutError
360
361
class TestErrorHandling:
362
def test_http_404_handling(self):
363
"""Test handling of HTTP 404 errors."""
364
# Mock 404 response
365
from bravado.testing.response_mocks import IncomingResponseMock
366
367
mock_response = IncomingResponseMock(
368
status_code=404,
369
text='{"error": "Pet not found"}',
370
headers={'Content-Type': 'application/json'}
371
)
372
373
# Your error handling logic would go here
374
assert mock_response.status_code == 404
375
376
def test_timeout_with_fallback(self):
377
"""Test timeout handling with fallback results."""
378
fallback_mock = FallbackResultBravadoResponseMock(
379
exception=BravadoTimeoutError("Connection timeout")
380
)
381
382
fallback_data = {'error': 'Service temporarily unavailable'}
383
response = fallback_mock(fallback_result=fallback_data)
384
385
assert response.result == fallback_data
386
assert response.metadata.is_fallback_result is True
387
```
388
389
## Test Data Constants
390
391
The testing module provides predefined test data for integration tests:
392
393
```python { .api }
394
ROUTE_1_RESPONSE: dict # Sample API response data
395
ROUTE_2_RESPONSE: dict # Alternative response data
396
API_RESPONSE: dict # Complete API response structure
397
SWAGGER_SPEC_DICT: dict # Sample Swagger specification
398
```
399
400
These constants provide consistent test data across different test scenarios.
401
402
## Best Practices
403
404
1. **Use Mocks for Unit Tests**: Test your business logic without making real HTTP calls
405
2. **Use Integration Tests for HTTP Clients**: Verify actual HTTP client behavior
406
3. **Test Error Scenarios**: Include tests for timeouts, connection errors, and HTTP errors
407
4. **Test Fallback Behavior**: Verify fallback results work as expected
408
5. **Parameterize Tests**: Test both `.result()` and `.response()` methods
409
6. **Mock External Dependencies**: Don't make real API calls in tests
410
411
```python
412
# Good testing example
413
import pytest
414
from unittest.mock import patch, MagicMock
415
from bravado.testing.response_mocks import BravadoResponseMock, FallbackResultBravadoResponseMock
416
417
class TestPetService:
418
@pytest.fixture
419
def pet_service(self):
420
return PetService() # Your service class
421
422
@pytest.fixture
423
def mock_client(self):
424
"""Mock SwaggerClient for testing."""
425
client = MagicMock()
426
return client
427
428
def test_get_pet_success(self, pet_service, mock_client):
429
"""Test successful pet retrieval."""
430
# Setup
431
expected_pet = {'id': 1, 'name': 'Fluffy', 'status': 'available'}
432
mock_response = BravadoResponseMock(expected_pet)
433
mock_client.pet.getPetById.return_value = mock_response
434
435
pet_service.client = mock_client
436
437
# Execute
438
result = pet_service.get_pet(1)
439
440
# Verify
441
assert result['name'] == 'Fluffy'
442
mock_client.pet.getPetById.assert_called_once_with(petId=1)
443
444
def test_get_pet_with_timeout_fallback(self, pet_service, mock_client):
445
"""Test fallback behavior on timeout."""
446
# Setup fallback mock
447
fallback_mock = FallbackResultBravadoResponseMock()
448
mock_client.pet.getPetById.return_value = fallback_mock
449
450
pet_service.client = mock_client
451
452
# Execute with fallback
453
result = pet_service.get_pet_with_fallback(1)
454
455
# Verify fallback was used
456
assert result['name'] == 'Unknown Pet'
457
assert result['status'] == 'unavailable'
458
459
@pytest.mark.parametrize("status_code,expected_error", [
460
(404, HTTPNotFound),
461
(401, HTTPUnauthorized),
462
(500, HTTPInternalServerError)
463
])
464
def test_get_pet_http_errors(self, pet_service, mock_client, status_code, expected_error):
465
"""Test various HTTP error scenarios."""
466
# Setup error mock
467
mock_client.pet.getPetById.side_effect = expected_error(
468
response=MagicMock(status_code=status_code)
469
)
470
471
pet_service.client = mock_client
472
473
# Execute and verify error
474
with pytest.raises(expected_error):
475
pet_service.get_pet(1)
476
```