deep-mock 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deep_mock-0.1.0/LICENSE +21 -0
- deep_mock-0.1.0/PKG-INFO +539 -0
- deep_mock-0.1.0/README.md +506 -0
- deep_mock-0.1.0/pyproject.toml +65 -0
- deep_mock-0.1.0/setup.cfg +4 -0
- deep_mock-0.1.0/src/deep_mock/__init__.py +25 -0
- deep_mock-0.1.0/src/deep_mock/deep_mock.py +465 -0
- deep_mock-0.1.0/src/deep_mock/examples/__init__.py +1 -0
- deep_mock-0.1.0/src/deep_mock/examples/handlers/__init__.py +1 -0
- deep_mock-0.1.0/src/deep_mock/examples/handlers/user_handler.py +19 -0
- deep_mock-0.1.0/src/deep_mock/examples/handlers/user_handler_relative.py +20 -0
- deep_mock-0.1.0/src/deep_mock/examples/services/__init__.py +1 -0
- deep_mock-0.1.0/src/deep_mock/examples/services/cache.py +16 -0
- deep_mock-0.1.0/src/deep_mock/examples/services/database.py +35 -0
- deep_mock-0.1.0/src/deep_mock/examples/services/external_api.py +20 -0
- deep_mock-0.1.0/src/deep_mock/examples/services/external_api_relative.py +21 -0
- deep_mock-0.1.0/src/deep_mock/examples/services/user_service.py +13 -0
- deep_mock-0.1.0/src/deep_mock/py.typed +0 -0
- deep_mock-0.1.0/src/deep_mock.egg-info/PKG-INFO +539 -0
- deep_mock-0.1.0/src/deep_mock.egg-info/SOURCES.txt +23 -0
- deep_mock-0.1.0/src/deep_mock.egg-info/dependency_links.txt +1 -0
- deep_mock-0.1.0/src/deep_mock.egg-info/requires.txt +8 -0
- deep_mock-0.1.0/src/deep_mock.egg-info/top_level.txt +1 -0
- deep_mock-0.1.0/tests/test_deep_mock.py +8 -0
- deep_mock-0.1.0/tests/test_mock_sys_modules.py +654 -0
deep_mock-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
deep_mock-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deep-mock
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python mocking library
|
|
5
|
+
Author-email: Ogi Zmaj Dzedaj <ognjen.bozickovic@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/styoe/deep-mock
|
|
8
|
+
Project-URL: Repository, https://github.com/styoe/deep-mock
|
|
9
|
+
Project-URL: Issues, https://github.com/styoe/deep-mock/issues
|
|
10
|
+
Keywords: mock,testing,unittest,mocking
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Testing
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing :: Mocking
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
28
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
29
|
+
Requires-Dist: isort>=5.0; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
31
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# deep-mock
|
|
35
|
+
|
|
36
|
+
A Python mocking library that simplifies patching and handles edge cases that `unittest.mock.patch` cannot solve.
|
|
37
|
+
|
|
38
|
+
## The Problem
|
|
39
|
+
|
|
40
|
+
Python's standard `unittest.mock.patch` has limitations:
|
|
41
|
+
|
|
42
|
+
1. **You must patch at the right location** - If `module_b` imports `func` from `module_a`, you need to patch `module_b.func`, not `module_a.func`. This gets complicated with multiple imports.
|
|
43
|
+
|
|
44
|
+
2. **Module-level state is not recomputed** - If a module computes values at import time using the function you're mocking, those values remain stale:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# cache.py
|
|
48
|
+
from database import fetch_user
|
|
49
|
+
|
|
50
|
+
SYSTEM_USER = fetch_user("system") # Computed ONCE at import time
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Even if you patch `fetch_user`, `SYSTEM_USER` still has the original value.
|
|
54
|
+
|
|
55
|
+
3. **Indirect dependencies are invisible** - If `module_c` imports from `module_b` which imports from `module_a`, patching `module_a` won't affect `module_c`'s module-level state.
|
|
56
|
+
|
|
57
|
+
## The Solution
|
|
58
|
+
|
|
59
|
+
`deep-mock` solves all of these problems:
|
|
60
|
+
|
|
61
|
+
- **Patch once, apply everywhere** - Patches propagate to all modules that imported the mocked function
|
|
62
|
+
- **Auto-reload modules** - Module-level state is automatically recomputed with mocked values
|
|
63
|
+
- **Handle edge cases** - `import_and_reload_module` for indirect dependencies
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install deep-mock
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from unittest.mock import Mock
|
|
75
|
+
from deep_mock import MockSysModules
|
|
76
|
+
|
|
77
|
+
mock_fetch = Mock(return_value={"id": "1", "name": "Test User"})
|
|
78
|
+
|
|
79
|
+
with MockSysModules([
|
|
80
|
+
("myapp.database", "fetch_user", mock_fetch),
|
|
81
|
+
]):
|
|
82
|
+
# All modules that import fetch_user now use the mock
|
|
83
|
+
# Module-level state is recomputed with the mock
|
|
84
|
+
from myapp.services import user_service
|
|
85
|
+
assert user_service.get_user("1")["name"] == "Test User"
|
|
86
|
+
|
|
87
|
+
# After exiting, everything is restored to original
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Examples
|
|
91
|
+
|
|
92
|
+
### Example 1: Simple Patching
|
|
93
|
+
|
|
94
|
+
The most basic use case - mock a function and all its imports are automatically patched.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from unittest.mock import Mock
|
|
98
|
+
from deep_mock import MockSysModules
|
|
99
|
+
|
|
100
|
+
# Create your mock
|
|
101
|
+
mock_db_connect = Mock(return_value={"connected": True})
|
|
102
|
+
|
|
103
|
+
# Use MockSysModules context manager
|
|
104
|
+
with MockSysModules([
|
|
105
|
+
("myapp.database", "connect", mock_db_connect),
|
|
106
|
+
]):
|
|
107
|
+
from myapp.api import handler
|
|
108
|
+
|
|
109
|
+
result = handler.process_request()
|
|
110
|
+
|
|
111
|
+
# Assert the mock was called
|
|
112
|
+
mock_db_connect.assert_called_once()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Example 2: Module-Level State (Direct Dependencies)
|
|
116
|
+
|
|
117
|
+
When a module computes values at import time, `deep-mock` automatically reloads it.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# myapp/cache.py
|
|
121
|
+
from myapp.database import fetch_user
|
|
122
|
+
|
|
123
|
+
# This runs ONCE at import time
|
|
124
|
+
SYSTEM_USER = fetch_user("system")
|
|
125
|
+
|
|
126
|
+
def get_system_user():
|
|
127
|
+
return SYSTEM_USER
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
# test_cache.py
|
|
132
|
+
from unittest.mock import Mock
|
|
133
|
+
from deep_mock import MockSysModules
|
|
134
|
+
|
|
135
|
+
def test_system_user_is_mocked():
|
|
136
|
+
mock_fetch = Mock(return_value={"id": "system", "name": "Mock Admin"})
|
|
137
|
+
|
|
138
|
+
with MockSysModules([
|
|
139
|
+
("myapp.database", "fetch_user", mock_fetch),
|
|
140
|
+
]):
|
|
141
|
+
from myapp.cache import get_system_user
|
|
142
|
+
|
|
143
|
+
# SYSTEM_USER was recomputed with the mock!
|
|
144
|
+
assert get_system_user()["name"] == "Mock Admin"
|
|
145
|
+
|
|
146
|
+
# After exiting, SYSTEM_USER is restored to real value
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Example 3: Indirect Dependencies (Edge Case)
|
|
150
|
+
|
|
151
|
+
When module C depends on module B which depends on module A, and you mock something in A:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# myapp/database.py
|
|
155
|
+
def fetch_user(user_id):
|
|
156
|
+
return {"id": user_id, "name": "Real User"}
|
|
157
|
+
|
|
158
|
+
# myapp/cache.py
|
|
159
|
+
from myapp.database import fetch_user
|
|
160
|
+
SYSTEM_USER = fetch_user("system")
|
|
161
|
+
|
|
162
|
+
def get_system_user():
|
|
163
|
+
return SYSTEM_USER
|
|
164
|
+
|
|
165
|
+
# myapp/user_service.py
|
|
166
|
+
from myapp.cache import get_system_user
|
|
167
|
+
|
|
168
|
+
# Indirect dependency - imports from cache, not database
|
|
169
|
+
USER_GREETING = f"Hello, {get_system_user()['name']}!"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# test_indirect.py
|
|
174
|
+
from unittest.mock import Mock
|
|
175
|
+
from deep_mock import MockSysModules, import_and_reload_module
|
|
176
|
+
|
|
177
|
+
def test_indirect_dependency():
|
|
178
|
+
mock_fetch = Mock(return_value={"id": "system", "name": "Mock User"})
|
|
179
|
+
|
|
180
|
+
# Import user_service BEFORE mocking
|
|
181
|
+
from myapp import user_service
|
|
182
|
+
assert user_service.USER_GREETING == "Hello, Real User!"
|
|
183
|
+
|
|
184
|
+
with MockSysModules([
|
|
185
|
+
("myapp.database", "fetch_user", mock_fetch),
|
|
186
|
+
]):
|
|
187
|
+
# cache is auto-reloaded (direct dependency)
|
|
188
|
+
from myapp.cache import get_system_user
|
|
189
|
+
assert get_system_user()["name"] == "Mock User"
|
|
190
|
+
|
|
191
|
+
# user_service is NOT auto-reloaded (indirect dependency)
|
|
192
|
+
# Its USER_GREETING still has the old value!
|
|
193
|
+
assert user_service.USER_GREETING == "Hello, Real User!"
|
|
194
|
+
|
|
195
|
+
# Use import_and_reload_module to fix this
|
|
196
|
+
user_service = import_and_reload_module("myapp.user_service")
|
|
197
|
+
assert user_service.USER_GREETING == "Hello, Mock User!"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Example 4: Mocking Multiple Functions
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from unittest.mock import Mock
|
|
204
|
+
from deep_mock import MockSysModules
|
|
205
|
+
|
|
206
|
+
mock_fetch = Mock(return_value={"id": "1", "name": "Test"})
|
|
207
|
+
mock_save = Mock(return_value=True)
|
|
208
|
+
mock_delete = Mock(return_value=True)
|
|
209
|
+
|
|
210
|
+
with MockSysModules([
|
|
211
|
+
("myapp.database", "fetch_user", mock_fetch),
|
|
212
|
+
("myapp.database", "save_user", mock_save),
|
|
213
|
+
("myapp.database", "delete_user", mock_delete),
|
|
214
|
+
]):
|
|
215
|
+
# All three functions are mocked everywhere
|
|
216
|
+
pass
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Example 5: Mocking Classes
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from unittest.mock import Mock
|
|
223
|
+
from deep_mock import MockSysModules
|
|
224
|
+
|
|
225
|
+
# Create a mock class
|
|
226
|
+
MockDatabaseClient = Mock()
|
|
227
|
+
mock_instance = Mock()
|
|
228
|
+
mock_instance.connect.return_value = {"status": "connected"}
|
|
229
|
+
mock_instance.query.return_value = [{"id": 1}]
|
|
230
|
+
MockDatabaseClient.return_value = mock_instance
|
|
231
|
+
|
|
232
|
+
with MockSysModules([
|
|
233
|
+
("myapp.database", "DatabaseClient", MockDatabaseClient),
|
|
234
|
+
]):
|
|
235
|
+
from myapp.services import data_service
|
|
236
|
+
|
|
237
|
+
result = data_service.get_all_records()
|
|
238
|
+
MockDatabaseClient.assert_called_once()
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Configuration with conftest.py
|
|
242
|
+
|
|
243
|
+
Set project-wide defaults in your `conftest.py`:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
# conftest.py
|
|
247
|
+
from deep_mock import DeepMockConfig
|
|
248
|
+
|
|
249
|
+
def pytest_configure(config):
|
|
250
|
+
DeepMockConfig.configure(
|
|
251
|
+
base_dir="src", # Base directory to scan for imports
|
|
252
|
+
allowed_dirs=["src/myapp"], # Only scan these directories
|
|
253
|
+
)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Now all `MockSysModules` usage will use these defaults:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
# test_something.py
|
|
260
|
+
from deep_mock import MockSysModules
|
|
261
|
+
|
|
262
|
+
# Uses conftest.py defaults automatically
|
|
263
|
+
with MockSysModules([("myapp.database", "fetch_user", mock)]):
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
# Override for specific test if needed
|
|
267
|
+
with MockSysModules(
|
|
268
|
+
[("myapp.database", "fetch_user", mock)],
|
|
269
|
+
base_dir="other_dir",
|
|
270
|
+
):
|
|
271
|
+
pass
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Debugging Mock Calls
|
|
275
|
+
|
|
276
|
+
Use the debugging utilities to inspect mock calls:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
from unittest.mock import Mock
|
|
280
|
+
from deep_mock import MockSysModules, print_all_mock_calls, find_calls_in_mock_calls
|
|
281
|
+
|
|
282
|
+
mock_db = Mock()
|
|
283
|
+
|
|
284
|
+
with MockSysModules([("myapp.database", "db", mock_db)]):
|
|
285
|
+
from myapp.services import user_service
|
|
286
|
+
user_service.create_user({"name": "Alice"})
|
|
287
|
+
user_service.create_user({"name": "Bob"})
|
|
288
|
+
|
|
289
|
+
# Print all calls for debugging
|
|
290
|
+
print_all_mock_calls(mock_db)
|
|
291
|
+
|
|
292
|
+
# Find specific calls
|
|
293
|
+
save_calls = find_calls_in_mock_calls(
|
|
294
|
+
mock_db,
|
|
295
|
+
"save",
|
|
296
|
+
call_filter=lambda args, kwargs: args[0]["name"] == "Alice"
|
|
297
|
+
)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## API Reference
|
|
301
|
+
|
|
302
|
+
### `MockSysModules`
|
|
303
|
+
|
|
304
|
+
Context manager for mocking with automatic module reloading.
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
class MockSysModules:
|
|
308
|
+
def __init__(
|
|
309
|
+
self,
|
|
310
|
+
override_modules: list[tuple[str, str, Any]] | None = None,
|
|
311
|
+
base_dir: str | None = None,
|
|
312
|
+
allowed_dirs: list[str] | None = None,
|
|
313
|
+
):
|
|
314
|
+
"""
|
|
315
|
+
Args:
|
|
316
|
+
override_modules: List of (module_name, attribute_name, mock) tuples.
|
|
317
|
+
- module_name: Full module path (e.g., "myapp.database")
|
|
318
|
+
- attribute_name: Name of the function/class to mock (e.g., "fetch_user")
|
|
319
|
+
- mock: The mock object to replace it with
|
|
320
|
+
|
|
321
|
+
base_dir: Base directory to scan for modules that import the mocked
|
|
322
|
+
attributes. Defaults to DeepMockConfig.base_dir or ".".
|
|
323
|
+
|
|
324
|
+
allowed_dirs: List of directories to limit scanning to. If None,
|
|
325
|
+
scans all directories under base_dir. Defaults to
|
|
326
|
+
DeepMockConfig.allowed_dirs.
|
|
327
|
+
"""
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Behavior:**
|
|
331
|
+
|
|
332
|
+
1. **On enter (`__enter__`):**
|
|
333
|
+
- Patches the specified attributes in the source modules
|
|
334
|
+
- Finds all loaded modules that imported these attributes
|
|
335
|
+
- Patches those modules too
|
|
336
|
+
- Reloads all affected modules so module-level state is recomputed with mocks
|
|
337
|
+
|
|
338
|
+
2. **On exit (`__exit__`):**
|
|
339
|
+
- Restores all original attributes
|
|
340
|
+
- Reloads all affected modules so module-level state is recomputed with real values
|
|
341
|
+
- Also reloads modules that were imported during the context
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
### `mock_sys_modules`
|
|
346
|
+
|
|
347
|
+
Function version of `MockSysModules`. Returns a cleanup function.
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
def mock_sys_modules(
|
|
351
|
+
override_modules: list[tuple[str, str, Any]] | None = None,
|
|
352
|
+
base_dir: str = ".",
|
|
353
|
+
allowed_dirs: list[str] | None = None,
|
|
354
|
+
) -> Callable[[], None]:
|
|
355
|
+
"""
|
|
356
|
+
Apply mocks and return a cleanup function.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
override_modules: List of (module_name, attribute_name, mock) tuples.
|
|
360
|
+
base_dir: Base directory to scan for imports.
|
|
361
|
+
allowed_dirs: Directories to limit scanning to.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
A cleanup function that restores original values and reloads modules.
|
|
365
|
+
|
|
366
|
+
Example:
|
|
367
|
+
cleanup = mock_sys_modules([("myapp.db", "fetch", mock)])
|
|
368
|
+
try:
|
|
369
|
+
# ... test code ...
|
|
370
|
+
finally:
|
|
371
|
+
cleanup()
|
|
372
|
+
"""
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
### `import_and_reload_module`
|
|
378
|
+
|
|
379
|
+
Import a module, or reload it if already imported. Essential for handling indirect dependencies.
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
def import_and_reload_module(module_name: str) -> ModuleType:
|
|
383
|
+
"""
|
|
384
|
+
Import or reload a module, returning the module object.
|
|
385
|
+
|
|
386
|
+
This is necessary for modules with INDIRECT dependencies on mocked functions.
|
|
387
|
+
These modules import from other modules (not directly from the mocked module),
|
|
388
|
+
so they are not automatically detected and reloaded by MockSysModules.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
module_name: Full module path (e.g., "myapp.services.user_service")
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
The imported/reloaded module object.
|
|
395
|
+
|
|
396
|
+
Example:
|
|
397
|
+
# user_service imports from cache, which imports from database
|
|
398
|
+
# When we mock database.fetch_user, user_service is not auto-reloaded
|
|
399
|
+
|
|
400
|
+
with MockSysModules([("myapp.database", "fetch_user", mock)]):
|
|
401
|
+
# Manually reload to recompute module-level state
|
|
402
|
+
user_service = import_and_reload_module("myapp.services.user_service")
|
|
403
|
+
assert user_service.CACHED_VALUE == "mocked value"
|
|
404
|
+
"""
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**When to use:**
|
|
408
|
+
|
|
409
|
+
- Module has module-level state computed from an indirect dependency
|
|
410
|
+
- Module was imported before entering `MockSysModules` and has indirect dependencies
|
|
411
|
+
- You need to force a reload at a specific point in your test
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
### `DeepMockConfig`
|
|
416
|
+
|
|
417
|
+
Global configuration for `deep-mock` defaults. Configure once in `conftest.py`.
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
class DeepMockConfig:
|
|
421
|
+
base_dir: str = "."
|
|
422
|
+
allowed_dirs: list[str] | None = None
|
|
423
|
+
|
|
424
|
+
@classmethod
|
|
425
|
+
def configure(
|
|
426
|
+
cls,
|
|
427
|
+
base_dir: str | None = None,
|
|
428
|
+
allowed_dirs: list[str] | None = None,
|
|
429
|
+
):
|
|
430
|
+
"""
|
|
431
|
+
Set default values for MockSysModules.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
base_dir: Default base directory for scanning modules.
|
|
435
|
+
allowed_dirs: Default directories to limit scanning to.
|
|
436
|
+
|
|
437
|
+
Example:
|
|
438
|
+
# In conftest.py
|
|
439
|
+
def pytest_configure(config):
|
|
440
|
+
DeepMockConfig.configure(
|
|
441
|
+
base_dir="src",
|
|
442
|
+
allowed_dirs=["src/myapp", "src/lib"],
|
|
443
|
+
)
|
|
444
|
+
"""
|
|
445
|
+
|
|
446
|
+
@classmethod
|
|
447
|
+
def reset(cls):
|
|
448
|
+
"""Reset configuration to defaults."""
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
### `find_calls_in_mock_calls`
|
|
454
|
+
|
|
455
|
+
Filter mock call history by name and optional predicate.
|
|
456
|
+
|
|
457
|
+
```python
|
|
458
|
+
def find_calls_in_mock_calls(
|
|
459
|
+
mock,
|
|
460
|
+
call_name: str,
|
|
461
|
+
call_filter: Callable[[tuple, dict[str, Any]], bool] | None = None,
|
|
462
|
+
) -> list[tuple[str, tuple, dict]]:
|
|
463
|
+
"""
|
|
464
|
+
Find specific calls in a mock's call history.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
mock: The mock object to inspect.
|
|
468
|
+
call_name: Name of the method call to find (e.g., "save", "().query").
|
|
469
|
+
call_filter: Optional function (args, kwargs) -> bool to filter calls.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
List of (call_name, args, kwargs) tuples matching the criteria.
|
|
473
|
+
|
|
474
|
+
Example:
|
|
475
|
+
# Find all 'save' calls where the first arg has status='active'
|
|
476
|
+
calls = find_calls_in_mock_calls(
|
|
477
|
+
mock_db,
|
|
478
|
+
"save",
|
|
479
|
+
call_filter=lambda args, kwargs: args[0]["status"] == "active"
|
|
480
|
+
)
|
|
481
|
+
assert len(calls) == 2
|
|
482
|
+
"""
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
### `print_all_mock_calls`
|
|
488
|
+
|
|
489
|
+
Debug utility to print all calls made to a mock.
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
def print_all_mock_calls(mock):
|
|
493
|
+
"""
|
|
494
|
+
Print all calls made to a mock object for debugging.
|
|
495
|
+
|
|
496
|
+
Prints each call with:
|
|
497
|
+
- Call name (e.g., "", "().method", "().method().chain")
|
|
498
|
+
- Call args (tuple)
|
|
499
|
+
- Call kwargs (dict)
|
|
500
|
+
|
|
501
|
+
Example output:
|
|
502
|
+
--------------------------------
|
|
503
|
+
Printing all mock calls
|
|
504
|
+
--------------------------------
|
|
505
|
+
Call name - type: <class 'str'> ().collection
|
|
506
|
+
Call args - type: <class 'tuple'> ('users',)
|
|
507
|
+
Call kwargs - type: <class 'dict'> {}
|
|
508
|
+
--------------------------------
|
|
509
|
+
"""
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
### `fake_useless_decorator`
|
|
515
|
+
|
|
516
|
+
A pass-through decorator for replacing real decorators in tests.
|
|
517
|
+
|
|
518
|
+
```python
|
|
519
|
+
def fake_useless_decorator(func):
|
|
520
|
+
"""
|
|
521
|
+
A decorator that does nothing - just returns the function as-is.
|
|
522
|
+
|
|
523
|
+
Useful for mocking decorators that have side effects you want to avoid
|
|
524
|
+
in tests (e.g., caching, authentication, rate limiting).
|
|
525
|
+
|
|
526
|
+
Example:
|
|
527
|
+
with MockSysModules([
|
|
528
|
+
("myapp.decorators", "require_auth", fake_useless_decorator),
|
|
529
|
+
("myapp.decorators", "cache_result", fake_useless_decorator),
|
|
530
|
+
]):
|
|
531
|
+
# Decorators are now no-ops
|
|
532
|
+
from myapp.api import handler
|
|
533
|
+
handler.protected_endpoint() # No auth check
|
|
534
|
+
"""
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## License
|
|
538
|
+
|
|
539
|
+
MIT
|