0
# Testing and Mocking
1
2
The Google API Python Client provides comprehensive testing utilities including HTTP mocking, response simulation, and sequence testing to enable effective unit testing without making real API calls.
3
4
## Capabilities
5
6
### HTTP Mocking Classes
7
8
Mock HTTP responses for testing API interactions without network calls.
9
10
```python { .api }
11
class HttpMock:
12
"""Mock HTTP responses for testing Google API operations."""
13
14
def __init__(self, filename=None, headers=None):
15
"""
16
Initialize HTTP mock with static response.
17
18
Args:
19
filename (str, optional): Path to file containing mock response content
20
headers (dict, optional): HTTP headers to include in mock response
21
"""
22
23
def request(self, uri, method='GET', body=None, headers=None,
24
redirections=1, connection_type=None):
25
"""
26
Return a mock HTTP response.
27
28
Args:
29
uri (str): Request URI (ignored in basic mock)
30
method (str): HTTP method (ignored in basic mock)
31
body (str, optional): Request body (ignored in basic mock)
32
headers (dict, optional): Request headers (ignored in basic mock)
33
redirections (int): Max redirections (ignored in basic mock)
34
connection_type: Connection type (ignored in basic mock)
35
36
Returns:
37
tuple: (httplib2.Response, content) - mock response and content
38
"""
39
40
class HttpMockSequence:
41
"""Mock a sequence of HTTP responses for testing multiple requests."""
42
43
def __init__(self, iterable):
44
"""
45
Initialize mock sequence with predefined responses.
46
47
Args:
48
iterable: Sequence of (response, content) tuples to return in order
49
"""
50
51
def request(self, uri, method='GET', body=None, headers=None,
52
redirections=1, connection_type=None):
53
"""
54
Return the next mock HTTP response in the sequence.
55
56
Args:
57
uri (str): Request URI (logged but ignored)
58
method (str): HTTP method (logged but ignored)
59
body (str, optional): Request body (logged but ignored)
60
headers (dict, optional): Request headers (logged but ignored)
61
redirections (int): Max redirections (ignored)
62
connection_type: Connection type (ignored)
63
64
Returns:
65
tuple: (httplib2.Response, content) - next mock response and content
66
67
Raises:
68
StopIteration: When all mock responses have been consumed
69
"""
70
```
71
72
## Usage Examples
73
74
### Basic HTTP Mocking
75
76
```python
77
from googleapiclient import discovery
78
from googleapiclient.http import HttpMock
79
import json
80
81
# Create mock response content
82
mock_response = {
83
'messages': [
84
{'id': '1', 'threadId': 'thread1'},
85
{'id': '2', 'threadId': 'thread2'}
86
]
87
}
88
89
# Create mock HTTP client
90
http_mock = HttpMock(headers={'status': '200'})
91
# You can also use a file: HttpMock('path/to/response.json')
92
93
# Build service with mock HTTP client
94
service = discovery.build('gmail', 'v1', http=http_mock)
95
96
# Make API call - returns mock data instead of real API call
97
result = service.users().messages().list(userId='me').execute()
98
print(result) # Will print the mock_response data
99
```
100
101
### File-Based Mocking
102
103
```python
104
from googleapiclient.http import HttpMock
105
import json
106
import tempfile
107
import os
108
109
# Create temporary mock response file
110
mock_data = {
111
'id': 'mock_message_id',
112
'payload': {
113
'headers': [
114
{'name': 'Subject', 'value': 'Mock Email Subject'},
115
{'name': 'From', 'value': 'test@example.com'}
116
]
117
}
118
}
119
120
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
121
json.dump(mock_data, f)
122
mock_file = f.name
123
124
try:
125
# Create mock with file
126
http_mock = HttpMock(filename=mock_file, headers={'status': '200'})
127
service = discovery.build('gmail', 'v1', http=http_mock)
128
129
# Test API call
130
message = service.users().messages().get(userId='me', id='test_id').execute()
131
print(f"Subject: {message['payload']['headers'][0]['value']}")
132
133
finally:
134
# Clean up
135
os.unlink(mock_file)
136
```
137
138
### Sequence Mocking for Multiple Requests
139
140
```python
141
from googleapiclient.http import HttpMockSequence
142
import httplib2
143
import json
144
145
# Create a sequence of mock responses
146
responses = [
147
# First request - list messages
148
(
149
httplib2.Response({'status': 200}),
150
json.dumps({
151
'messages': [
152
{'id': 'msg1', 'threadId': 'thread1'},
153
{'id': 'msg2', 'threadId': 'thread2'}
154
]
155
}).encode('utf-8')
156
),
157
# Second request - get first message
158
(
159
httplib2.Response({'status': 200}),
160
json.dumps({
161
'id': 'msg1',
162
'payload': {
163
'headers': [
164
{'name': 'Subject', 'value': 'First Message'},
165
{'name': 'From', 'value': 'sender1@example.com'}
166
]
167
}
168
}).encode('utf-8')
169
),
170
# Third request - get second message
171
(
172
httplib2.Response({'status': 200}),
173
json.dumps({
174
'id': 'msg2',
175
'payload': {
176
'headers': [
177
{'name': 'Subject', 'value': 'Second Message'},
178
{'name': 'From', 'value': 'sender2@example.com'}
179
]
180
}
181
}).encode('utf-8')
182
)
183
]
184
185
# Create sequence mock
186
http_mock = HttpMockSequence(responses)
187
service = discovery.build('gmail', 'v1', http=http_mock)
188
189
# Make sequential API calls - each gets the next response
190
messages_list = service.users().messages().list(userId='me').execute()
191
print(f"Found {len(messages_list['messages'])} messages")
192
193
for msg_info in messages_list['messages']:
194
message = service.users().messages().get(userId='me', id=msg_info['id']).execute()
195
subject = next(h['value'] for h in message['payload']['headers'] if h['name'] == 'Subject')
196
print(f"Message: {subject}")
197
```
198
199
### Error Response Mocking
200
201
```python
202
from googleapiclient.http import HttpMock
203
from googleapiclient.errors import HttpError
204
import httplib2
205
import json
206
207
# Mock an error response
208
error_content = {
209
'error': {
210
'code': 404,
211
'message': 'Requested entity was not found.',
212
'errors': [
213
{
214
'domain': 'global',
215
'reason': 'notFound',
216
'message': 'Requested entity was not found.'
217
}
218
]
219
}
220
}
221
222
# Create mock with error response
223
http_mock = HttpMock(headers={'status': '404'})
224
service = discovery.build('gmail', 'v1', http=http_mock)
225
226
try:
227
# This will raise an HttpError with the mocked error
228
message = service.users().messages().get(userId='me', id='nonexistent').execute()
229
except HttpError as error:
230
print(f'Caught expected error: {error.status_code}')
231
print(f'Error reason: {error._get_reason()}')
232
```
233
234
### Unit Testing with pytest
235
236
```python
237
import pytest
238
from googleapiclient import discovery
239
from googleapiclient.http import HttpMock, HttpMockSequence
240
from googleapiclient.errors import HttpError
241
import json
242
import httplib2
243
244
class TestGmailService:
245
"""Test class for Gmail service operations."""
246
247
@pytest.fixture
248
def mock_service(self):
249
"""Create a Gmail service with HTTP mocking."""
250
http_mock = HttpMock(headers={'status': '200'})
251
return discovery.build('gmail', 'v1', http=http_mock)
252
253
def test_list_messages(self, mock_service):
254
"""Test listing messages with mock response."""
255
# The mock will return default response
256
result = mock_service.users().messages().list(userId='me').execute()
257
assert 'messages' in result or result == {} # Depends on mock setup
258
259
def test_get_message_success(self):
260
"""Test getting a message with successful mock response."""
261
mock_response = {
262
'id': 'test_message_id',
263
'payload': {
264
'headers': [
265
{'name': 'Subject', 'value': 'Test Subject'},
266
{'name': 'From', 'value': 'test@example.com'}
267
]
268
}
269
}
270
271
# Create mock with specific response
272
http_mock = HttpMock(headers={'status': '200'})
273
service = discovery.build('gmail', 'v1', http=http_mock)
274
275
# Mock returns predefined response
276
result = service.users().messages().get(userId='me', id='test_id').execute()
277
# In real implementation, you'd set up the mock to return mock_response
278
279
def test_http_error_handling(self):
280
"""Test HTTP error handling with mock error response."""
281
# Mock 404 error
282
http_mock = HttpMock(headers={'status': '404'})
283
service = discovery.build('gmail', 'v1', http=http_mock)
284
285
with pytest.raises(HttpError) as exc_info:
286
service.users().messages().get(userId='me', id='missing').execute()
287
288
assert exc_info.value.status_code == 404
289
290
def test_batch_operations(self):
291
"""Test batch operations with sequence mocking."""
292
responses = [
293
(httplib2.Response({'status': 200}), b'{"id": "msg1"}'),
294
(httplib2.Response({'status': 200}), b'{"id": "msg2"}'),
295
(httplib2.Response({'status': 404}), b'{"error": {"code": 404}}')
296
]
297
298
http_mock = HttpMockSequence(responses)
299
service = discovery.build('gmail', 'v1', http=http_mock)
300
301
# First two calls succeed, third fails
302
result1 = service.users().messages().get(userId='me', id='msg1').execute()
303
result2 = service.users().messages().get(userId='me', id='msg2').execute()
304
305
with pytest.raises(HttpError):
306
service.users().messages().get(userId='me', id='missing').execute()
307
```
308
309
### Integration Testing Pattern
310
311
```python
312
import unittest
313
from googleapiclient import discovery
314
from googleapiclient.http import HttpMockSequence
315
import httplib2
316
import json
317
318
class GmailApiTest(unittest.TestCase):
319
"""Integration tests for Gmail API operations."""
320
321
def setUp(self):
322
"""Set up test fixtures."""
323
self.test_messages = [
324
{
325
'id': 'msg1',
326
'payload': {'headers': [{'name': 'Subject', 'value': 'Test 1'}]}
327
},
328
{
329
'id': 'msg2',
330
'payload': {'headers': [{'name': 'Subject', 'value': 'Test 2'}]}
331
}
332
]
333
334
def test_message_workflow(self):
335
"""Test complete message retrieval workflow."""
336
# Create sequence of responses for workflow
337
responses = [
338
# List messages response
339
(
340
httplib2.Response({'status': 200}),
341
json.dumps({
342
'messages': [
343
{'id': 'msg1', 'threadId': 'thread1'},
344
{'id': 'msg2', 'threadId': 'thread2'}
345
]
346
}).encode('utf-8')
347
),
348
# Get message 1
349
(
350
httplib2.Response({'status': 200}),
351
json.dumps(self.test_messages[0]).encode('utf-8')
352
),
353
# Get message 2
354
(
355
httplib2.Response({'status': 200}),
356
json.dumps(self.test_messages[1]).encode('utf-8')
357
)
358
]
359
360
http_mock = HttpMockSequence(responses)
361
service = discovery.build('gmail', 'v1', http=http_mock)
362
363
# Execute workflow
364
messages_list = service.users().messages().list(userId='me').execute()
365
self.assertEqual(len(messages_list['messages']), 2)
366
367
# Get each message
368
for i, msg_info in enumerate(messages_list['messages']):
369
message = service.users().messages().get(
370
userId='me',
371
id=msg_info['id']
372
).execute()
373
374
expected_subject = f'Test {i + 1}'
375
actual_subject = message['payload']['headers'][0]['value']
376
self.assertEqual(actual_subject, expected_subject)
377
378
if __name__ == '__main__':
379
unittest.main()
380
```
381
382
### Advanced Mock Patterns
383
384
```python
385
from googleapiclient.http import HttpMock
386
import httplib2
387
import json
388
389
class CustomHttpMock:
390
"""Custom HTTP mock with request inspection and dynamic responses."""
391
392
def __init__(self):
393
self.requests = [] # Track all requests made
394
self.responses = {} # Map URI patterns to responses
395
396
def add_response(self, uri_pattern, response_data, status=200):
397
"""Add a response for a specific URI pattern."""
398
self.responses[uri_pattern] = (status, response_data)
399
400
def request(self, uri, method='GET', body=None, headers=None,
401
redirections=1, connection_type=None):
402
"""Mock request with dynamic response based on URI."""
403
404
# Log the request
405
self.requests.append({
406
'uri': uri,
407
'method': method,
408
'body': body,
409
'headers': headers
410
})
411
412
# Find matching response
413
for pattern, (status, data) in self.responses.items():
414
if pattern in uri:
415
response = httplib2.Response({'status': status})
416
content = json.dumps(data).encode('utf-8') if isinstance(data, dict) else data
417
return response, content
418
419
# Default response
420
response = httplib2.Response({'status': 200})
421
return response, b'{}'
422
423
def get_requests(self):
424
"""Get all requests that were made."""
425
return self.requests.copy()
426
427
# Usage example
428
mock = CustomHttpMock()
429
mock.add_response('messages', {'messages': []})
430
mock.add_response('labels', {'labels': [{'id': 'INBOX', 'name': 'INBOX'}]})
431
432
service = discovery.build('gmail', 'v1', http=mock)
433
434
# Make some requests
435
service.users().messages().list(userId='me').execute()
436
service.users().labels().list(userId='me').execute()
437
438
# Inspect what requests were made
439
requests = mock.get_requests()
440
print(f"Made {len(requests)} requests:")
441
for req in requests:
442
print(f" {req['method']} {req['uri']}")
443
```