@martinffx/atelier 0.1.0
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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/agents/architect.md +38 -0
- package/agents/oracle.md +40 -0
- package/agents/recon.md +39 -0
- package/dist/atelier.js +33082 -0
- package/package.json +44 -0
- package/skills/code-conventional-commit/SKILL.md +134 -0
- package/skills/code-conventional-commit/evals/evals.json +23 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/commit-message.txt +4 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/validation-result.txt +16 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/commit-message.txt +8 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/report.html +1325 -0
- package/skills/code-debug/SKILL.md +271 -0
- package/skills/code-debug/references/language-tools.md +139 -0
- package/skills/code-debug/references/techniques.md +117 -0
- package/skills/code-docs/SKILL.md +171 -0
- package/skills/code-docs/references/api-docs.md +116 -0
- package/skills/code-docs/references/changelog.md +64 -0
- package/skills/code-docs/references/readme-structure.md +94 -0
- package/skills/code-git-worktrees/SKILL.md +218 -0
- package/skills/code-perf/SKILL.md +163 -0
- package/skills/code-perf/references/common-bottlenecks.md +124 -0
- package/skills/code-perf/references/optimization-patterns.md +167 -0
- package/skills/code-perf/references/profiling-tools.md +126 -0
- package/skills/code-review/SKILL.md +78 -0
- package/skills/code-review/references/output.md +200 -0
- package/skills/code-review/references/reviewers.md +719 -0
- package/skills/code-review/references/rq.md +357 -0
- package/skills/code-review/references/rs.md +30 -0
- package/skills/code-security/SKILL.md +183 -0
- package/skills/code-security/references/owasp-top-10.md +129 -0
- package/skills/code-security/references/security-tools.md +102 -0
- package/skills/code-security/references/vulnerability-patterns.md +129 -0
- package/skills/code-stacked-commit/SKILL.md +155 -0
- package/skills/code-subagents/SKILL.md +184 -0
- package/skills/code-subagents/references/code-quality-reviewer-prompt.md +48 -0
- package/skills/code-subagents/references/implementor-prompt.md +79 -0
- package/skills/code-subagents/references/spec-reviewer-prompt.md +62 -0
- package/skills/oracle-architect/SKILL.md +246 -0
- package/skills/oracle-architect/references/api-design.md +345 -0
- package/skills/oracle-architect/references/data-modeling.md +318 -0
- package/skills/oracle-architect/references/ddd-patterns.md +247 -0
- package/skills/oracle-challenge/SKILL.md +126 -0
- package/skills/oracle-testing/SKILL.md +237 -0
- package/skills/oracle-testing/references/anti-patterns.md +618 -0
- package/skills/oracle-testing/references/boundaries.md +579 -0
- package/skills/oracle-testing/references/core-testing.md +691 -0
- package/skills/oracle-testing/references/edge-testing.md +794 -0
- package/skills/oracle-testing/references/stub-driven-tdd.md +322 -0
- package/skills/oracle-thinkdeep/SKILL.md +164 -0
- package/skills/python-architecture/SKILL.md +148 -0
- package/skills/python-architecture/references/data-modeling.md +558 -0
- package/skills/python-architecture/references/ddd.md +569 -0
- package/skills/python-architecture/references/functional-core.md +337 -0
- package/skills/python-build-tools/SKILL.md +153 -0
- package/skills/python-build-tools/references/basedpyright.md +321 -0
- package/skills/python-build-tools/references/ruff.md +225 -0
- package/skills/python-build-tools/references/uv.md +257 -0
- package/skills/python-fastapi/SKILL.md +184 -0
- package/skills/python-fastapi/references/api-design.md +563 -0
- package/skills/python-fastapi/references/dependencies.md +479 -0
- package/skills/python-fastapi/references/middleware.md +475 -0
- package/skills/python-fastapi/references/validation.md +497 -0
- package/skills/python-modern-python/SKILL.md +201 -0
- package/skills/python-modern-python/references/async.md +76 -0
- package/skills/python-modern-python/references/pattern-matching.md +89 -0
- package/skills/python-modern-python/references/typing.md +96 -0
- package/skills/python-monorepo/SKILL.md +146 -0
- package/skills/python-monorepo/references/docker.md +515 -0
- package/skills/python-monorepo/references/namespace-packages.md +426 -0
- package/skills/python-monorepo/references/workspace-config.md +341 -0
- package/skills/python-sqlalchemy/SKILL.md +119 -0
- package/skills/python-sqlalchemy/references/async.md +51 -0
- package/skills/python-sqlalchemy/references/models.md +67 -0
- package/skills/python-sqlalchemy/references/queries.md +57 -0
- package/skills/python-temporal/SKILL.md +128 -0
- package/skills/python-temporal/references/patterns.md +87 -0
- package/skills/python-temporal/references/testing.md +54 -0
- package/skills/python-testing/SKILL.md +161 -0
- package/skills/python-testing/references/boundaries.md +449 -0
- package/skills/python-testing/references/mocking.md +449 -0
- package/skills/python-testing/references/pytest.md +524 -0
- package/skills/spec-brainstorm/SKILL.md +325 -0
- package/skills/spec-finish/SKILL.md +159 -0
- package/skills/spec-implement/SKILL.md +296 -0
- package/skills/spec-orchestrator/SKILL.md +156 -0
- package/skills/spec-plan/SKILL.md +248 -0
- package/skills/typescript-api-design/SKILL.md +396 -0
- package/skills/typescript-api-design/references/error-responses.md +469 -0
- package/skills/typescript-build-tools/SKILL.md +447 -0
- package/skills/typescript-build-tools/references/biome.md +436 -0
- package/skills/typescript-build-tools/references/bun.md +432 -0
- package/skills/typescript-build-tools/references/turborepo.md +426 -0
- package/skills/typescript-build-tools/references/vitest.md +518 -0
- package/skills/typescript-drizzle-orm/SKILL.md +398 -0
- package/skills/typescript-drizzle-orm/references/cloudflare.md +392 -0
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +412 -0
- package/skills/typescript-drizzle-orm/references/postgresql.md +191 -0
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +479 -0
- package/skills/typescript-drizzle-orm/references/sqlite.md +291 -0
- package/skills/typescript-dynamodb-toolbox/SKILL.md +370 -0
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +574 -0
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +495 -0
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +332 -0
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +631 -0
- package/skills/typescript-dynamodb-toolbox/references/testing.md +718 -0
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +423 -0
- package/skills/typescript-effect-ts/SKILL.md +341 -0
- package/skills/typescript-effect-ts/references/core.md +412 -0
- package/skills/typescript-effect-ts/references/error-handling.md +468 -0
- package/skills/typescript-effect-ts/references/resources.md +476 -0
- package/skills/typescript-effect-ts/references/schema.md +536 -0
- package/skills/typescript-effect-ts/references/services.md +461 -0
- package/skills/typescript-fastify/SKILL.md +405 -0
- package/skills/typescript-fastify/references/plugins.md +329 -0
- package/skills/typescript-fastify/references/typeid.md +315 -0
- package/skills/typescript-functional-patterns/SKILL.md +402 -0
- package/skills/typescript-functional-patterns/references/adts.md +497 -0
- package/skills/typescript-functional-patterns/references/branded-types.md +542 -0
- package/skills/typescript-functional-patterns/references/migration-guide.md +695 -0
- package/skills/typescript-functional-patterns/references/option-result.md +585 -0
- package/skills/typescript-testing/SKILL.md +353 -0
- package/skills/typescript-testing/references/mocking.md +675 -0
- package/skills/typescript-testing/references/msw.md +852 -0
- package/skills/typescript-testing/references/snapshot-testing.md +774 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# Mocking and Stubbing Strategies
|
|
2
|
+
|
|
3
|
+
Stub external dependencies to isolate units under test. Use at layer boundaries.
|
|
4
|
+
|
|
5
|
+
## unittest.mock Basics
|
|
6
|
+
|
|
7
|
+
### Mock Objects
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from unittest.mock import Mock
|
|
11
|
+
|
|
12
|
+
# Create mock
|
|
13
|
+
mock_repo = Mock()
|
|
14
|
+
|
|
15
|
+
# Configure return value
|
|
16
|
+
mock_repo.get.return_value = User(id=1, name="Alice")
|
|
17
|
+
|
|
18
|
+
# Call mock
|
|
19
|
+
user = mock_repo.get(1)
|
|
20
|
+
|
|
21
|
+
# Verify it was called
|
|
22
|
+
mock_repo.get.assert_called_once_with(1)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### MagicMock for Chaining
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from unittest.mock import MagicMock
|
|
29
|
+
|
|
30
|
+
# MagicMock supports method chaining
|
|
31
|
+
mock_db = MagicMock()
|
|
32
|
+
mock_db.query.return_value.filter.return_value.first.return_value = UserRecord()
|
|
33
|
+
|
|
34
|
+
# Chain calls work
|
|
35
|
+
user = mock_db.query(User).filter(User.id == 1).first()
|
|
36
|
+
assert user is not None
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### patch Decorator
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from unittest.mock import patch
|
|
43
|
+
|
|
44
|
+
# Patch module-level dependency
|
|
45
|
+
@patch("my_module.DatabaseSession")
|
|
46
|
+
def test_with_patched_db(mock_db_class):
|
|
47
|
+
"""Patch is applied for duration of test"""
|
|
48
|
+
mock_db_class.return_value = Mock()
|
|
49
|
+
|
|
50
|
+
service = UserService() # Gets patched DatabaseSession
|
|
51
|
+
# Test code
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### patch as Context Manager
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from unittest.mock import patch
|
|
58
|
+
|
|
59
|
+
def test_with_patch_context():
|
|
60
|
+
"""Patch applied only within context"""
|
|
61
|
+
with patch("my_module.external_api_call") as mock_api:
|
|
62
|
+
mock_api.return_value = {"status": "success"}
|
|
63
|
+
|
|
64
|
+
result = process_data()
|
|
65
|
+
|
|
66
|
+
assert result["status"] == "success"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### spec Parameter
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from unittest.mock import Mock
|
|
73
|
+
|
|
74
|
+
# Mock with spec restricts to real attributes
|
|
75
|
+
mock_repo = Mock(spec=UserRepository)
|
|
76
|
+
|
|
77
|
+
# This works (real method on UserRepository)
|
|
78
|
+
mock_repo.save()
|
|
79
|
+
|
|
80
|
+
# This fails with AttributeError (not real method)
|
|
81
|
+
with pytest.raises(AttributeError):
|
|
82
|
+
mock_repo.invalid_method()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Verification Methods
|
|
86
|
+
|
|
87
|
+
### assert_called_once
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
mock_obj = Mock()
|
|
91
|
+
|
|
92
|
+
# Called exactly once
|
|
93
|
+
mock_obj.method()
|
|
94
|
+
mock_obj.method.assert_called_once()
|
|
95
|
+
|
|
96
|
+
# With arguments
|
|
97
|
+
mock_obj.method(42, "test")
|
|
98
|
+
mock_obj.method.assert_called_once_with(42, "test")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### assert_called
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
mock_obj = Mock()
|
|
105
|
+
|
|
106
|
+
# Called at least once
|
|
107
|
+
mock_obj.method()
|
|
108
|
+
mock_obj.method()
|
|
109
|
+
mock_obj.method.assert_called() # Passes
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### assert_called_with
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
mock_obj = Mock()
|
|
116
|
+
|
|
117
|
+
# Called with specific arguments
|
|
118
|
+
mock_obj.method(1, 2, 3)
|
|
119
|
+
mock_obj.method.assert_called_with(1, 2, 3) # Passes
|
|
120
|
+
|
|
121
|
+
mock_obj.method.assert_called_with(1, 2) # Fails - wrong args
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### assert_not_called
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
mock_obj = Mock()
|
|
128
|
+
|
|
129
|
+
# Never called
|
|
130
|
+
mock_obj.method.assert_not_called() # Passes
|
|
131
|
+
|
|
132
|
+
mock_obj.method()
|
|
133
|
+
mock_obj.method.assert_not_called() # Fails
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### call_count
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
mock_obj = Mock()
|
|
140
|
+
|
|
141
|
+
mock_obj.method()
|
|
142
|
+
mock_obj.method()
|
|
143
|
+
mock_obj.method()
|
|
144
|
+
|
|
145
|
+
assert mock_obj.method.call_count == 3
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Multiple Calls
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from unittest.mock import call
|
|
152
|
+
|
|
153
|
+
mock_obj = Mock()
|
|
154
|
+
|
|
155
|
+
mock_obj.method(1)
|
|
156
|
+
mock_obj.method(2)
|
|
157
|
+
mock_obj.method(3)
|
|
158
|
+
|
|
159
|
+
mock_obj.method.assert_has_calls([
|
|
160
|
+
call(1),
|
|
161
|
+
call(2),
|
|
162
|
+
call(3),
|
|
163
|
+
])
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## pytest-mock Integration
|
|
167
|
+
|
|
168
|
+
### mocker Fixture
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
def test_with_mocker(mocker):
|
|
172
|
+
"""pytest-mock provides cleaner patching"""
|
|
173
|
+
# Patch and get mock in one call
|
|
174
|
+
mock_db = mocker.patch("my_module.get_db")
|
|
175
|
+
mock_db.return_value = Mock()
|
|
176
|
+
|
|
177
|
+
service = UserService()
|
|
178
|
+
# Test code
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Compared to unittest.mock
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
# unittest.mock (verbose)
|
|
185
|
+
@patch("my_module.get_db")
|
|
186
|
+
def test_with_patch(mock_db):
|
|
187
|
+
mock_db.return_value = Mock()
|
|
188
|
+
...
|
|
189
|
+
|
|
190
|
+
# pytest-mock (cleaner)
|
|
191
|
+
def test_with_mocker(mocker):
|
|
192
|
+
mock_db = mocker.patch("my_module.get_db")
|
|
193
|
+
mock_db.return_value = Mock()
|
|
194
|
+
...
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Common Mocking Patterns
|
|
198
|
+
|
|
199
|
+
### Service with Mocked Repository
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
def test_create_product_service():
|
|
203
|
+
"""Mock repository dependency"""
|
|
204
|
+
# Create mock
|
|
205
|
+
mock_repo = Mock(spec=ProductRepository)
|
|
206
|
+
mock_repo.save.return_value = Product(
|
|
207
|
+
id=uuid4(),
|
|
208
|
+
name="Widget",
|
|
209
|
+
price=Decimal("9.99"),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Create service with mock
|
|
213
|
+
service = ProductService(repo=mock_repo)
|
|
214
|
+
|
|
215
|
+
# Call service
|
|
216
|
+
request = CreateProductRequest(name="Widget", price=Decimal("9.99"))
|
|
217
|
+
result = service.create(request)
|
|
218
|
+
|
|
219
|
+
# Verify interactions
|
|
220
|
+
mock_repo.save.assert_called_once()
|
|
221
|
+
assert result.name == "Widget"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Service with Mocked External API
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
@patch("my_module.stripe.charge")
|
|
228
|
+
def test_process_payment(mock_stripe):
|
|
229
|
+
"""Mock external service call"""
|
|
230
|
+
mock_stripe.return_value = {"status": "succeeded"}
|
|
231
|
+
|
|
232
|
+
service = PaymentService()
|
|
233
|
+
result = service.process_payment(Decimal("99.99"))
|
|
234
|
+
|
|
235
|
+
mock_stripe.assert_called_once()
|
|
236
|
+
assert result["status"] == "succeeded"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Multiple Mocked Dependencies
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
def test_complex_workflow(mocker):
|
|
243
|
+
"""Multiple mocks in one test"""
|
|
244
|
+
mock_repo = mocker.patch("my_module.repository")
|
|
245
|
+
mock_api = mocker.patch("my_module.external_api")
|
|
246
|
+
mock_logger = mocker.patch("my_module.logger")
|
|
247
|
+
|
|
248
|
+
mock_repo.get.return_value = {"id": 1}
|
|
249
|
+
mock_api.call.return_value = {"result": "ok"}
|
|
250
|
+
|
|
251
|
+
service = ComplexService()
|
|
252
|
+
result = service.do_something(1)
|
|
253
|
+
|
|
254
|
+
mock_repo.get.assert_called_once_with(1)
|
|
255
|
+
mock_api.call.assert_called_once()
|
|
256
|
+
mock_logger.info.assert_called()
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Side Effects for Complex Behavior
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
def test_with_side_effect():
|
|
263
|
+
"""Mock returns different values on each call"""
|
|
264
|
+
mock_obj = Mock()
|
|
265
|
+
|
|
266
|
+
# Return different values
|
|
267
|
+
mock_obj.method.side_effect = [1, 2, 3]
|
|
268
|
+
|
|
269
|
+
assert mock_obj.method() == 1
|
|
270
|
+
assert mock_obj.method() == 2
|
|
271
|
+
assert mock_obj.method() == 3
|
|
272
|
+
|
|
273
|
+
def test_with_side_effect_exception():
|
|
274
|
+
"""Mock raises exception"""
|
|
275
|
+
mock_obj = Mock()
|
|
276
|
+
|
|
277
|
+
# Raise exception on call
|
|
278
|
+
mock_obj.method.side_effect = ValueError("Test error")
|
|
279
|
+
|
|
280
|
+
with pytest.raises(ValueError, match="Test error"):
|
|
281
|
+
mock_obj.method()
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Partial Mocking with spec_set
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
class UserRepository:
|
|
288
|
+
def get(self, id):
|
|
289
|
+
...
|
|
290
|
+
|
|
291
|
+
def save(self, user):
|
|
292
|
+
...
|
|
293
|
+
|
|
294
|
+
# spec_set prevents adding new attributes
|
|
295
|
+
mock_repo = Mock(spec_set=UserRepository)
|
|
296
|
+
|
|
297
|
+
mock_repo.get.return_value = User(id=1)
|
|
298
|
+
|
|
299
|
+
# This works
|
|
300
|
+
mock_repo.get(1)
|
|
301
|
+
|
|
302
|
+
# This fails - not on UserRepository
|
|
303
|
+
with pytest.raises(AttributeError):
|
|
304
|
+
mock_repo.new_method()
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Fixture-Based Mocking
|
|
308
|
+
|
|
309
|
+
### Reusable Mock Fixtures
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
import pytest
|
|
313
|
+
from unittest.mock import Mock
|
|
314
|
+
|
|
315
|
+
@pytest.fixture
|
|
316
|
+
def mock_repository():
|
|
317
|
+
"""Mock repository fixture"""
|
|
318
|
+
mock = Mock(spec=UserRepository)
|
|
319
|
+
mock.save.return_value = None
|
|
320
|
+
mock.get.return_value = User(id=1, name="Test")
|
|
321
|
+
return mock
|
|
322
|
+
|
|
323
|
+
@pytest.fixture
|
|
324
|
+
def mock_external_api():
|
|
325
|
+
"""Mock external API fixture"""
|
|
326
|
+
mock = Mock()
|
|
327
|
+
mock.call.return_value = {"status": "success"}
|
|
328
|
+
return mock
|
|
329
|
+
|
|
330
|
+
def test_service_with_fixtures(mock_repository, mock_external_api):
|
|
331
|
+
"""Use mocked dependencies"""
|
|
332
|
+
service = UserService(
|
|
333
|
+
repo=mock_repository,
|
|
334
|
+
api=mock_external_api,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
result = service.create_user({"name": "Alice"})
|
|
338
|
+
|
|
339
|
+
mock_repository.save.assert_called_once()
|
|
340
|
+
assert result is not None
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Testing with Real vs Mock Objects
|
|
344
|
+
|
|
345
|
+
### When to Mock
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
# GOOD: Mock external service
|
|
349
|
+
@patch("requests.post")
|
|
350
|
+
def test_send_notification(mock_post):
|
|
351
|
+
"""Don't actually call HTTP endpoints"""
|
|
352
|
+
mock_post.return_value = Mock(status_code=200)
|
|
353
|
+
|
|
354
|
+
service = NotificationService()
|
|
355
|
+
result = service.send_email("test@example.com")
|
|
356
|
+
|
|
357
|
+
assert result is True
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### When to Use Real Objects
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
# GOOD: Use real database for repository tests
|
|
364
|
+
def test_repository_save(test_db):
|
|
365
|
+
"""Test actual database behavior"""
|
|
366
|
+
repo = UserRepository(test_db)
|
|
367
|
+
user = User(id=1, name="Alice")
|
|
368
|
+
|
|
369
|
+
repo.save(user)
|
|
370
|
+
|
|
371
|
+
# Verify in database
|
|
372
|
+
assert test_db.query(UserRecord).count() == 1
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Hybrid: Real + Mock
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
def test_service_orchestration(test_db, mocker):
|
|
379
|
+
"""Real repository, mocked external API"""
|
|
380
|
+
# Real database
|
|
381
|
+
repo = UserRepository(test_db)
|
|
382
|
+
|
|
383
|
+
# Mocked external API
|
|
384
|
+
mock_api = mocker.patch("my_module.send_email")
|
|
385
|
+
|
|
386
|
+
# Test service orchestration
|
|
387
|
+
service = UserService(repo=repo, api=mock_api)
|
|
388
|
+
result = service.create_and_notify({"name": "Alice"})
|
|
389
|
+
|
|
390
|
+
# Both called
|
|
391
|
+
assert test_db.query(UserRecord).count() == 1
|
|
392
|
+
mock_api.assert_called_once()
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Anti-Patterns to Avoid
|
|
396
|
+
|
|
397
|
+
### Over-Mocking
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
# BAD: Mocking everything
|
|
401
|
+
def test_service():
|
|
402
|
+
mock_repo = Mock()
|
|
403
|
+
mock_entity = Mock()
|
|
404
|
+
mock_response = Mock()
|
|
405
|
+
# Test is now testing mocks, not code
|
|
406
|
+
|
|
407
|
+
# GOOD: Mock at boundaries, use real objects internally
|
|
408
|
+
def test_service(test_db):
|
|
409
|
+
repo = UserRepository(test_db) # Real
|
|
410
|
+
service = UserService(repo) # Real
|
|
411
|
+
result = service.get_user(1) # Test actual behavior
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Tautological Mocks
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
# BAD: Mock returns what we programmed it to return
|
|
418
|
+
def test_something():
|
|
419
|
+
mock = Mock()
|
|
420
|
+
mock.method.return_value = 42
|
|
421
|
+
assert mock.method() == 42 # Always passes, tests nothing
|
|
422
|
+
|
|
423
|
+
# GOOD: Test real logic
|
|
424
|
+
def test_something():
|
|
425
|
+
obj = RealObject()
|
|
426
|
+
result = obj.calculate()
|
|
427
|
+
assert result == expected
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Mock Drift
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
# BAD: Mock doesn't match reality
|
|
434
|
+
class RealService:
|
|
435
|
+
def process(self, data):
|
|
436
|
+
return data.upper()
|
|
437
|
+
|
|
438
|
+
def test_service():
|
|
439
|
+
mock_service = Mock()
|
|
440
|
+
mock_service.process.return_value = "WRONG" # Not what real does
|
|
441
|
+
|
|
442
|
+
# Test passes but would fail with real service
|
|
443
|
+
|
|
444
|
+
# GOOD: Mock matches real behavior or test with real
|
|
445
|
+
def test_service():
|
|
446
|
+
service = RealService()
|
|
447
|
+
result = service.process("test")
|
|
448
|
+
assert result == "TEST"
|
|
449
|
+
```
|