0
# Mock Creation and Management
1
2
Advanced mock creation utilities and specialized mock objects for sophisticated testing scenarios. These capabilities extend beyond basic patching to provide spies, stubs, and automatic specification-based mocks.
3
4
## Capabilities
5
6
### Automatic Specification Mocks
7
8
Creates mocks that automatically match the interface of existing objects, providing better type safety and catching interface mismatches during testing.
9
10
```python { .api }
11
def create_autospec(
12
self,
13
spec: Any,
14
spec_set: bool = False,
15
instance: bool = False,
16
**kwargs: Any
17
) -> MockType:
18
"""
19
Create a mock with automatic specification from an existing object.
20
21
Parameters:
22
- spec: Object to use as the specification template
23
- spec_set: If True, only allow access to attributes that exist on spec
24
- instance: If True, mock represents an instance of spec rather than spec itself
25
- **kwargs: Additional arguments passed to mock constructor
26
27
Returns:
28
Mock object that matches the spec's interface
29
"""
30
```
31
32
Usage examples:
33
34
```python
35
class DatabaseClient:
36
def connect(self, host: str, port: int) -> bool:
37
return True
38
39
def query(self, sql: str) -> List[Dict]:
40
return []
41
42
def close(self) -> None:
43
pass
44
45
def test_autospec_class(mocker):
46
# Create mock that matches DatabaseClient interface
47
mock_client = mocker.create_autospec(DatabaseClient)
48
mock_client.connect.return_value = True
49
mock_client.query.return_value = [{'id': 1, 'name': 'test'}]
50
51
# Mock enforces correct method signatures
52
result = mock_client.connect('localhost', 5432) # OK
53
data = mock_client.query('SELECT * FROM users') # OK
54
55
# This would raise AttributeError - method doesn't exist on spec
56
# mock_client.invalid_method()
57
58
mock_client.connect.assert_called_with('localhost', 5432)
59
60
def test_autospec_instance(mocker):
61
# Create mock representing an instance of DatabaseClient
62
mock_instance = mocker.create_autospec(DatabaseClient, instance=True)
63
mock_instance.connect.return_value = True
64
65
# Use like an instance
66
setup_database(mock_instance)
67
68
mock_instance.connect.assert_called()
69
70
def test_autospec_function(mocker):
71
def complex_function(a: int, b: str, c: bool = False) -> Dict:
72
return {'result': a + len(b)}
73
74
# Mock enforces function signature
75
mock_func = mocker.create_autospec(complex_function)
76
mock_func.return_value = {'result': 42}
77
78
# Correct call
79
result = mock_func(10, 'hello', c=True)
80
assert result == {'result': 42}
81
82
# This would raise TypeError - wrong signature
83
# mock_func(10) # Missing required argument 'b'
84
```
85
86
### Spy Objects
87
88
Creates spy objects that call the original method while recording all interactions, perfect for verifying method calls without changing behavior.
89
90
```python { .api }
91
def spy(self, obj: object, name: str) -> MockType:
92
"""
93
Create a spy that calls the original method and records calls.
94
95
The spy maintains the original behavior while providing mock
96
capabilities for verification and introspection.
97
98
Parameters:
99
- obj: Object containing the method to spy on
100
- name: Name of the method to spy on
101
102
Returns:
103
Mock object that wraps the original method
104
105
Spy attributes:
106
- spy_return: Return value of the last call
107
- spy_return_list: List of all return values
108
- spy_exception: Exception from last call (if any)
109
"""
110
```
111
112
Usage examples:
113
114
```python
115
class FileProcessor:
116
def process_file(self, filepath: str) -> Dict:
117
# Actually processes the file
118
with open(filepath, 'r') as f:
119
content = f.read()
120
return {'size': len(content), 'processed': True}
121
122
def test_spy_method_calls(mocker, tmp_path):
123
# Create test file
124
test_file = tmp_path / "test.txt"
125
test_file.write_text("Hello World")
126
127
processor = FileProcessor()
128
129
# Spy on the method - it still works normally
130
spy = mocker.spy(processor, 'process_file')
131
132
# Call the method - file is actually processed
133
result = processor.process_file(str(test_file))
134
135
# Verify the real behavior occurred
136
assert result['size'] == 11
137
assert result['processed'] is True
138
139
# Verify the spy recorded the call
140
spy.assert_called_once_with(str(test_file))
141
assert spy.spy_return == result
142
assert spy.spy_return_list == [result]
143
144
def test_spy_multiple_calls(mocker):
145
calculator = Calculator()
146
spy = mocker.spy(calculator, 'add')
147
148
# Make multiple calls
149
result1 = calculator.add(2, 3) # Returns 5
150
result2 = calculator.add(10, 20) # Returns 30
151
152
# Verify all calls were recorded
153
assert spy.call_count == 2
154
spy.assert_has_calls([
155
mocker.call(2, 3),
156
mocker.call(10, 20)
157
])
158
159
assert spy.spy_return_list == [5, 30]
160
161
def test_spy_exception_handling(mocker):
162
processor = FileProcessor()
163
spy = mocker.spy(processor, 'process_file')
164
165
# Call with invalid file - raises exception
166
with pytest.raises(FileNotFoundError):
167
processor.process_file('nonexistent.txt')
168
169
# Spy recorded the exception
170
assert isinstance(spy.spy_exception, FileNotFoundError)
171
spy.assert_called_once_with('nonexistent.txt')
172
173
async def test_async_spy(mocker):
174
class AsyncProcessor:
175
async def fetch_data(self, url: str) -> Dict:
176
# Actual async operation
177
await asyncio.sleep(0.1)
178
return {'url': url, 'status': 'success'}
179
180
processor = AsyncProcessor()
181
spy = mocker.spy(processor, 'fetch_data')
182
183
# Call the async method
184
result = await processor.fetch_data('https://api.example.com')
185
186
# Verify spy recorded the async call
187
assert result['status'] == 'success'
188
spy.assert_called_once_with('https://api.example.com')
189
assert spy.spy_return == result
190
```
191
192
### Stub Objects
193
194
Creates stub objects that accept any arguments and return configurable values, ideal for callback testing and placeholder implementations.
195
196
```python { .api }
197
def stub(self, name: Optional[str] = None) -> unittest.mock.MagicMock:
198
"""
199
Create a stub method that accepts any arguments.
200
201
Parameters:
202
- name: Optional name for the stub (used in repr)
203
204
Returns:
205
MagicMock configured as a stub
206
"""
207
208
def async_stub(self, name: Optional[str] = None) -> AsyncMockType:
209
"""
210
Create an async stub method that accepts any arguments.
211
212
Parameters:
213
- name: Optional name for the stub (used in repr)
214
215
Returns:
216
AsyncMock configured as a stub
217
"""
218
```
219
220
Usage examples:
221
222
```python
223
def test_callback_with_stub(mocker):
224
# Create a stub for callback testing
225
callback = mocker.stub(name='data_callback')
226
227
# Configure the stub's behavior
228
callback.return_value = 'processed'
229
230
# Use stub in code that expects a callback
231
def process_data(data, callback_fn):
232
result = callback_fn(data)
233
return f"Result: {result}"
234
235
# Test with the stub
236
output = process_data({'id': 1}, callback)
237
238
assert output == "Result: processed"
239
callback.assert_called_once_with({'id': 1})
240
241
def test_multiple_callbacks(mocker):
242
# Create multiple stubs for different callbacks
243
on_start = mocker.stub(name='on_start')
244
on_complete = mocker.stub(name='on_complete')
245
on_error = mocker.stub(name='on_error')
246
247
# Configure different return values
248
on_start.return_value = None
249
on_complete.return_value = 'success'
250
on_error.return_value = None
251
252
# Use in code that takes multiple callbacks
253
processor = DataProcessor()
254
processor.process(
255
data={'items': [1, 2, 3]},
256
on_start=on_start,
257
on_complete=on_complete,
258
on_error=on_error
259
)
260
261
# Verify callback usage
262
on_start.assert_called_once()
263
on_complete.assert_called_once_with('success')
264
on_error.assert_not_called()
265
266
async def test_async_callback_stub(mocker):
267
# Create async stub for async callbacks
268
async_callback = mocker.async_stub(name='async_processor')
269
async_callback.return_value = {'status': 'completed'}
270
271
async def process_async(data, callback):
272
result = await callback(data)
273
return result['status']
274
275
# Test with async stub
276
status = await process_async({'id': 1}, async_callback)
277
278
assert status == 'completed'
279
async_callback.assert_called_once_with({'id': 1})
280
281
def test_flexible_stub_arguments(mocker):
282
# Stub accepts any arguments without complaint
283
flexible_stub = mocker.stub(name='flexible')
284
flexible_stub.return_value = 'ok'
285
286
# All these calls work - stub accepts anything
287
assert flexible_stub() == 'ok'
288
assert flexible_stub('arg') == 'ok'
289
assert flexible_stub('arg1', 'arg2') == 'ok'
290
assert flexible_stub(a=1, b=2) == 'ok'
291
assert flexible_stub('pos', keyword='value') == 'ok'
292
293
# Verify all calls were recorded
294
assert flexible_stub.call_count == 5
295
```
296
297
### Mock Class Aliases
298
299
Direct access to mock classes from the underlying mock module, providing convenient access to all mock types without importing unittest.mock.
300
301
```python { .api }
302
# Mock class aliases (available as mocker attributes)
303
Mock: Type[unittest.mock.Mock]
304
MagicMock: Type[unittest.mock.MagicMock]
305
NonCallableMock: Type[unittest.mock.NonCallableMock]
306
NonCallableMagicMock: Type[unittest.mock.NonCallableMagicMock]
307
PropertyMock: Type[unittest.mock.PropertyMock]
308
AsyncMock: Type[unittest.mock.AsyncMock] # Python 3.8+
309
310
# Mock utilities
311
call: unittest.mock._Call
312
ANY: unittest.mock._AnyComparer
313
DEFAULT: unittest.mock._SentinelObject
314
sentinel: unittest.mock._SentinelObject
315
mock_open: Callable
316
seal: Callable # If available
317
```
318
319
Usage examples:
320
321
```python
322
def test_direct_mock_creation(mocker):
323
# Create mocks directly from mocker attributes
324
magic_mock = mocker.MagicMock()
325
magic_mock.method.return_value = 42
326
327
# Use in your code
328
result = magic_mock.method('arg')
329
assert result == 42
330
331
# Mock is automatically registered for cleanup
332
magic_mock.method.assert_called_once_with('arg')
333
334
def test_property_mock(mocker):
335
class MyClass:
336
@property
337
def value(self):
338
return "real_value"
339
340
obj = MyClass()
341
342
# Mock the property
343
prop_mock = mocker.PropertyMock(return_value="mocked_value")
344
mocker.patch.object(MyClass, 'value', new_callable=lambda: prop_mock)
345
346
assert obj.value == "mocked_value"
347
prop_mock.assert_called_once()
348
349
def test_mock_constants(mocker):
350
# Use ANY to match any argument
351
mock_func = mocker.patch('mymodule.function')
352
353
mymodule.function('some_arg')
354
mock_func.assert_called_with(mocker.ANY)
355
356
# Use call to construct call objects
357
expected_calls = [
358
mocker.call('arg1'),
359
mocker.call('arg2')
360
]
361
362
mymodule.function('arg1')
363
mymodule.function('arg2')
364
365
mock_func.assert_has_calls(expected_calls)
366
367
def test_mock_open(mocker):
368
# Mock file operations
369
mock_file = mocker.mock_open(read_data="file content")
370
mocker.patch('builtins.open', mock_file)
371
372
with open('test.txt', 'r') as f:
373
content = f.read()
374
375
assert content == "file content"
376
mock_file.assert_called_once_with('test.txt', 'r')
377
378
def test_async_mock(mocker):
379
async def async_function():
380
return "async_result"
381
382
# Mock async function
383
mock_async = mocker.AsyncMock(return_value="mocked_result")
384
mocker.patch('mymodule.async_function', mock_async)
385
386
# Test async code
387
import asyncio
388
result = asyncio.run(mymodule.async_function())
389
390
assert result == "mocked_result"
391
mock_async.assert_awaited_once()
392
```
393
394
### Mock Utilities
395
396
Essential utilities for advanced mock assertions and configurations, providing fine-grained control over mock behavior and verification.
397
398
```python { .api }
399
# Call construction and matching
400
call: unittest.mock._Call
401
ANY: unittest.mock._AnyComparer
402
DEFAULT: unittest.mock._SentinelObject
403
sentinel: unittest.mock._SentinelObject
404
mock_open: Callable
405
seal: Callable # If available
406
```
407
408
Usage examples:
409
410
```python
411
def test_call_utility(mocker):
412
"""Using call to verify complex call patterns"""
413
mock_func = mocker.patch('mymodule.complex_function')
414
415
# Make multiple calls
416
mymodule.complex_function('arg1', key='value1')
417
mymodule.complex_function('arg2', key='value2')
418
419
# Verify specific calls
420
expected_calls = [
421
mocker.call('arg1', key='value1'),
422
mocker.call('arg2', key='value2')
423
]
424
mock_func.assert_has_calls(expected_calls)
425
426
def test_any_utility(mocker):
427
"""Using ANY to match flexible arguments"""
428
mock_func = mocker.patch('mymodule.function')
429
430
# Call with any arguments
431
mymodule.function('specific_arg', some_dynamic_value=42)
432
433
# Verify call with partial matching
434
mock_func.assert_called_with('specific_arg', some_dynamic_value=mocker.ANY)
435
436
# Mix specific and ANY arguments
437
mock_func.assert_called_with(mocker.ANY, some_dynamic_value=42)
438
439
def test_default_utility(mocker):
440
"""Using DEFAULT for partial patching"""
441
original_func = mymodule.function
442
443
# Patch with DEFAULT to preserve some behavior
444
mock_func = mocker.patch('mymodule.function')
445
mock_func.side_effect = lambda x: original_func(x) if x > 0 else mocker.DEFAULT
446
447
# DEFAULT returns the default mock behavior for negative values
448
result = mymodule.function(-1) # Returns DEFAULT mock behavior
449
assert isinstance(result, unittest.mock.MagicMock)
450
451
def test_sentinel_utility(mocker):
452
"""Using sentinel for unique sentinel values"""
453
# Create unique sentinel values for testing
454
MISSING = mocker.sentinel.MISSING
455
INVALID = mocker.sentinel.INVALID
456
457
mock_func = mocker.patch('mymodule.function')
458
mock_func.return_value = MISSING
459
460
result = mymodule.function()
461
assert result is MISSING # Identity comparison
462
assert result is not INVALID # Different sentinels
463
464
def test_mock_open_utility(mocker):
465
"""Using mock_open for file operations"""
466
# Mock reading a file
467
file_content = "line 1\nline 2\nline 3"
468
mock_file = mocker.mock_open(read_data=file_content)
469
mocker.patch('builtins.open', mock_file)
470
471
# Test reading
472
with open('test.txt', 'r') as f:
473
content = f.read()
474
lines = f.readlines() # This will be empty due to file pointer
475
476
assert content == file_content
477
mock_file.assert_called_once_with('test.txt', 'r')
478
479
# Test writing
480
mock_write_file = mocker.mock_open()
481
mocker.patch('builtins.open', mock_write_file)
482
483
with open('output.txt', 'w') as f:
484
f.write('test content')
485
486
mock_write_file.assert_called_once_with('output.txt', 'w')
487
mock_write_file().write.assert_called_once_with('test content')
488
489
def test_seal_utility(mocker):
490
"""Using seal to prevent mock attribute creation"""
491
if hasattr(mocker, 'seal'): # seal is only available in Python 3.7+
492
mock_obj = mocker.MagicMock()
493
mock_obj.existing_attr = 'value'
494
495
# Seal the mock to prevent new attribute creation
496
mocker.seal(mock_obj)
497
498
# Existing attributes still work
499
assert mock_obj.existing_attr == 'value'
500
501
# New attributes raise AttributeError
502
with pytest.raises(AttributeError):
503
_ = mock_obj.new_attr
504
```