@martinffx/atelier 1.2.2 → 1.4.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/.codex-plugin/plugin.json +27 -0
- package/CHANGELOG.md +33 -0
- package/README.md +51 -59
- package/agents/architect.md +1 -1
- package/agents/oracle.md +1 -1
- package/dist/atelier.js +14171 -12949
- package/package.json +6 -1
- package/skills/code-commit/SKILL.md +77 -77
- package/skills/code-handoff/SKILL.md +1 -1
- package/skills/code-pull-request/SKILL.md +36 -0
- package/skills/code-pull-request/references/common.md +76 -0
- package/skills/code-pull-request/references/create-pr.md +122 -0
- package/skills/code-pull-request/references/default-template.md +25 -0
- package/skills/code-pull-request/references/leave-comment.md +53 -0
- package/skills/code-pull-request/references/merge-pr.md +94 -0
- package/skills/code-pull-request/references/read-comments.md +61 -0
- package/skills/code-pull-request/references/respond-to-comment.md +66 -0
- package/skills/code-review/SKILL.md +21 -12
- package/skills/code-review/references/reviewers.md +142 -41
- package/skills/code-review/references/rq.md +48 -32
- package/skills/oracle-debug/SKILL.md +218 -0
- package/skills/{code-debug → oracle-debug}/references/bug-patterns.md +3 -3
- package/skills/oracle-domain-modelling/SKILL.md +84 -0
- package/skills/oracle-grill-me/SKILL.md +20 -0
- package/skills/spec-brainstorm/SKILL.md +91 -71
- package/skills/spec-finish/SKILL.md +20 -14
- package/skills/spec-implement/SKILL.md +3 -2
- package/skills/spec-orchestrator/SKILL.md +4 -15
- package/skills/spec-plan/SKILL.md +1 -1
- package/agents/scout.md +0 -40
- package/skills/code-commit/references/pr-workflow.md +0 -145
- package/skills/code-debug/SKILL.md +0 -199
- package/skills/code-docs/SKILL.md +0 -171
- package/skills/code-docs/references/api-docs.md +0 -116
- package/skills/code-docs/references/changelog.md +0 -64
- package/skills/code-docs/references/readme-structure.md +0 -94
- package/skills/oracle-architect/SKILL.md +0 -327
- package/skills/oracle-architect/references/api-design.md +0 -345
- package/skills/oracle-architect/references/data-modeling.md +0 -318
- package/skills/oracle-architect/references/ddd-patterns.md +0 -348
- package/skills/oracle-architect/references/interface-design.md +0 -203
- package/skills/oracle-challenge/SKILL.md +0 -126
- package/skills/oracle-doubt/SKILL.md +0 -252
- package/skills/oracle-doubt/references/adversarial-prompt.md +0 -47
- package/skills/oracle-grillme/SKILL.md +0 -160
- package/skills/oracle-security/SKILL.md +0 -225
- package/skills/oracle-security/references/security-patterns.md +0 -236
- package/skills/oracle-security/references/threat-modeling-guide.md +0 -137
- package/skills/oracle-testing/SKILL.md +0 -237
- package/skills/oracle-testing/references/anti-patterns.md +0 -618
- package/skills/oracle-testing/references/boundaries.md +0 -579
- package/skills/oracle-testing/references/core-testing.md +0 -691
- package/skills/oracle-testing/references/edge-testing.md +0 -794
- package/skills/oracle-testing/references/stub-driven-tdd.md +0 -322
- package/skills/oracle-thinkdeep/SKILL.md +0 -164
- /package/skills/{code-debug → oracle-debug}/references/language-tools.md +0 -0
- /package/skills/{code-debug → oracle-debug}/references/techniques.md +0 -0
- /package/skills/{oracle-grillme → oracle-domain-modelling}/references/ADR-FORMAT.md +0 -0
- /package/skills/{oracle-grillme → oracle-domain-modelling}/references/CONTEXT-FORMAT.md +0 -0
|
@@ -1,618 +0,0 @@
|
|
|
1
|
-
# Testing Anti-Patterns
|
|
2
|
-
|
|
3
|
-
What NOT to test and why. Common testing mistakes with examples.
|
|
4
|
-
|
|
5
|
-
## Testing Implementation Details
|
|
6
|
-
|
|
7
|
-
Testing internal implementation forces refactors to update tests, even when behavior doesn't change.
|
|
8
|
-
|
|
9
|
-
### Anti-Pattern: Testing Private Methods
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
// ❌ DON'T: Test private methods
|
|
13
|
-
class OrderService {
|
|
14
|
-
private calculateTotal(items: OrderItem[]): number {
|
|
15
|
-
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async createOrder(request: CreateOrderRequest): Promise<Result<Order>> {
|
|
19
|
-
const total = this.calculateTotal(request.items);
|
|
20
|
-
// ...
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Test tries to call private method - won't compile
|
|
25
|
-
describe('OrderService', () => {
|
|
26
|
-
it('calculates total correctly', () => {
|
|
27
|
-
const service = new OrderService();
|
|
28
|
-
// Can't call private calculateTotal!
|
|
29
|
-
const total = service['calculateTotal']([...]);
|
|
30
|
-
expect(total).toBe(35);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
**Problem:**
|
|
36
|
-
- Can't directly test private methods
|
|
37
|
-
- Forced to use `service['calculateTotal']()` hack
|
|
38
|
-
- Breaking refactors: rename or move the method, test breaks
|
|
39
|
-
|
|
40
|
-
**Solution:** Test through public method
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
// ✓ DO: Test behavior through public method
|
|
44
|
-
describe('OrderService.createOrder', () => {
|
|
45
|
-
it('creates order with correct total', async () => {
|
|
46
|
-
const mockRepo = { save: vi.fn().mockResolvedValue({ id: '123' }) };
|
|
47
|
-
const service = new OrderService(mockRepo);
|
|
48
|
-
|
|
49
|
-
const result = await service.createOrder({
|
|
50
|
-
customerId: 'C1',
|
|
51
|
-
items: [
|
|
52
|
-
{ productId: 'P1', quantity: 2, price: 10 },
|
|
53
|
-
{ productId: 'P2', quantity: 1, price: 15 }
|
|
54
|
-
]
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
expect(mockRepo.save).toHaveBeenCalledWith(
|
|
58
|
-
expect.objectContaining({ total: 35 })
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Testing Trivial Code
|
|
65
|
-
|
|
66
|
-
Getters, setters, and simple assignments add test maintenance burden with zero value.
|
|
67
|
-
|
|
68
|
-
### Anti-Pattern: Testing Getters/Setters
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
// ❌ DON'T: Test trivial getters/setters
|
|
72
|
-
class Order {
|
|
73
|
-
constructor(private _id: string, private _customerId: string) {}
|
|
74
|
-
|
|
75
|
-
get id(): string {
|
|
76
|
-
return this._id;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
get customerId(): string {
|
|
80
|
-
return this._customerId;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
describe('Order', () => {
|
|
85
|
-
it('returns id', () => {
|
|
86
|
-
const order = new Order('123', 'C1');
|
|
87
|
-
expect(order.id).toBe('123');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('returns customerId', () => {
|
|
91
|
-
const order = new Order('123', 'C1');
|
|
92
|
-
expect(order.customerId).toBe('C1');
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**Problem:**
|
|
98
|
-
- Tests trivial property access
|
|
99
|
-
- Maintenance burden: change property, update test
|
|
100
|
-
- Compiler already validates property access
|
|
101
|
-
|
|
102
|
-
**Solution:** Only test getters with logic
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
// ✓ DO: Test getters with computed values
|
|
106
|
-
class Order {
|
|
107
|
-
constructor(
|
|
108
|
-
private _id: string,
|
|
109
|
-
private _customerId: string,
|
|
110
|
-
private _items: OrderItem[]
|
|
111
|
-
) {}
|
|
112
|
-
|
|
113
|
-
get id(): string {
|
|
114
|
-
return this._id;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
get total(): number {
|
|
118
|
-
return this._items.reduce((sum, item) => sum + item.price * item.quantity, 0);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
describe('Order', () => {
|
|
123
|
-
it('calculates total from items', () => {
|
|
124
|
-
const order = new Order('123', 'C1', [
|
|
125
|
-
{ productId: 'P1', quantity: 2, price: 10 },
|
|
126
|
-
{ productId: 'P2', quantity: 1, price: 15 }
|
|
127
|
-
]);
|
|
128
|
-
|
|
129
|
-
// Only test computed properties with logic
|
|
130
|
-
expect(order.total).toBe(35);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Testing Framework Behavior
|
|
136
|
-
|
|
137
|
-
Framework libraries are already tested. Don't test Express, Jest, or database driver behavior.
|
|
138
|
-
|
|
139
|
-
### Anti-Pattern: Testing Framework Features
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
// ❌ DON'T: Test Express parses JSON
|
|
143
|
-
import request from 'supertest';
|
|
144
|
-
import { Express } from 'express';
|
|
145
|
-
|
|
146
|
-
describe('Express integration', () => {
|
|
147
|
-
it('parses JSON request body', async () => {
|
|
148
|
-
const app: Express = express();
|
|
149
|
-
app.use(express.json());
|
|
150
|
-
|
|
151
|
-
app.post('/test', (req, res) => {
|
|
152
|
-
res.json(req.body);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
const response = await request(app)
|
|
156
|
-
.post('/test')
|
|
157
|
-
.send({ name: 'John' });
|
|
158
|
-
|
|
159
|
-
// Framework already does this!
|
|
160
|
-
expect(typeof response.body).toBe('object');
|
|
161
|
-
expect(response.body.name).toBe('John');
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// ❌ DON'T: Test database driver functionality
|
|
166
|
-
describe('Database driver', () => {
|
|
167
|
-
it('returns results as array', async () => {
|
|
168
|
-
const result = await db.query('SELECT * FROM orders');
|
|
169
|
-
|
|
170
|
-
// Driver already does this
|
|
171
|
-
expect(Array.isArray(result)).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
**Problem:**
|
|
177
|
-
- Framework/library already tested by maintainers
|
|
178
|
-
- Wastes test execution time
|
|
179
|
-
- No value - not testing your code
|
|
180
|
-
|
|
181
|
-
**Solution:** Test your code's integration with frameworks
|
|
182
|
-
|
|
183
|
-
```typescript
|
|
184
|
-
// ✓ DO: Test your integration with framework
|
|
185
|
-
describe('POST /orders', () => {
|
|
186
|
-
it('parses request and calls service', async () => {
|
|
187
|
-
const mockService = {
|
|
188
|
-
createOrder: vi.fn().mockResolvedValue(Ok({ id: '123' }))
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
const app = createApp(mockService);
|
|
192
|
-
|
|
193
|
-
const response = await request(app)
|
|
194
|
-
.post('/orders')
|
|
195
|
-
.send({ customerId: 'C1', items: [] });
|
|
196
|
-
|
|
197
|
-
// Test your code, not Express behavior
|
|
198
|
-
expect(mockService.createOrder).toHaveBeenCalledWith(
|
|
199
|
-
expect.objectContaining({ customerId: 'C1' })
|
|
200
|
-
);
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
## Testing Trivial Mappings
|
|
206
|
-
|
|
207
|
-
Simple property-to-property mappings without logic add maintenance burden.
|
|
208
|
-
|
|
209
|
-
### Anti-Pattern: Testing Simple Mappings
|
|
210
|
-
|
|
211
|
-
```typescript
|
|
212
|
-
// ❌ DON'T: Test trivial mappings
|
|
213
|
-
class OrderMapper {
|
|
214
|
-
toResponse(order: Order): OrderResponse {
|
|
215
|
-
return {
|
|
216
|
-
id: order.id,
|
|
217
|
-
customerId: order.customerId,
|
|
218
|
-
status: order.status,
|
|
219
|
-
total: order.total
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
describe('OrderMapper', () => {
|
|
225
|
-
it('maps order to response', () => {
|
|
226
|
-
const order = new Order('123', 'C1', [], 'pending', 0);
|
|
227
|
-
const response = OrderMapper.toResponse(order);
|
|
228
|
-
|
|
229
|
-
expect(response.id).toBe('123');
|
|
230
|
-
expect(response.customerId).toBe('C1');
|
|
231
|
-
expect(response.status).toBe('pending');
|
|
232
|
-
expect(response.total).toBe(0);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**Problem:**
|
|
238
|
-
- No logic to test - pure property copy
|
|
239
|
-
- Compiler validates mapping at type-check time
|
|
240
|
-
- Maintenance: change property, update test
|
|
241
|
-
|
|
242
|
-
**Solution:** Test mappings with logic or transformation
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
// ✓ DO: Test mappings with transformations
|
|
246
|
-
class OrderMapper {
|
|
247
|
-
// Includes logic: calculation, conditional, transformation
|
|
248
|
-
toResponse(order: Order): OrderResponse {
|
|
249
|
-
return {
|
|
250
|
-
id: order.id,
|
|
251
|
-
customerId: order.customerId,
|
|
252
|
-
status: order.status,
|
|
253
|
-
total: order.total,
|
|
254
|
-
displayTotal: `$${order.total.toFixed(2)}`,
|
|
255
|
-
// Only include recent orders
|
|
256
|
-
isRecent: order.createdAt > Date.now() - 7 * 24 * 60 * 60 * 1000
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
describe('OrderMapper', () => {
|
|
262
|
-
it('formats total with currency', () => {
|
|
263
|
-
const order = new Order('123', 'C1', [], 'pending', 35.5);
|
|
264
|
-
const response = OrderMapper.toResponse(order);
|
|
265
|
-
|
|
266
|
-
expect(response.displayTotal).toBe('$35.50');
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('marks recent orders', () => {
|
|
270
|
-
const order = new Order('123', 'C1', [], 'pending', 0, new Date());
|
|
271
|
-
const response = OrderMapper.toResponse(order);
|
|
272
|
-
|
|
273
|
-
expect(response.isRecent).toBe(true);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
## Testing Third-Party Libraries
|
|
279
|
-
|
|
280
|
-
Libraries have their own test suites. Test your integration, not their behavior.
|
|
281
|
-
|
|
282
|
-
### Anti-Pattern: Testing Library Behavior
|
|
283
|
-
|
|
284
|
-
```typescript
|
|
285
|
-
// ❌ DON'T: Test third-party library
|
|
286
|
-
import _ from 'lodash';
|
|
287
|
-
|
|
288
|
-
describe('lodash', () => {
|
|
289
|
-
it('groups array elements', () => {
|
|
290
|
-
const result = _.groupBy([1, 2, 3, 4], x => x % 2);
|
|
291
|
-
expect(result[0]).toEqual([2, 4]);
|
|
292
|
-
expect(result[1]).toEqual([1, 3]);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// ❌ DON'T: Test validation library
|
|
297
|
-
import Joi from 'joi';
|
|
298
|
-
|
|
299
|
-
describe('Joi validation', () => {
|
|
300
|
-
it('validates required field', () => {
|
|
301
|
-
const schema = Joi.object({
|
|
302
|
-
name: Joi.string().required()
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
const result = schema.validate({ name: 'John' });
|
|
306
|
-
expect(result.error).toBeUndefined();
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
**Problem:**
|
|
312
|
-
- Library already tested by maintainers
|
|
313
|
-
- No test coverage value - not testing your code
|
|
314
|
-
- Wasted execution time
|
|
315
|
-
|
|
316
|
-
**Solution:** Test your code that uses libraries
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
// ✓ DO: Test how you use the library
|
|
320
|
-
describe('OrderValidator', () => {
|
|
321
|
-
it('rejects order with missing customerId', () => {
|
|
322
|
-
const result = OrderValidator.validate({
|
|
323
|
-
// customerId missing
|
|
324
|
-
items: [{ productId: 'P1', quantity: 1 }]
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
expect(result.ok).toBe(false);
|
|
328
|
-
expect(result.error).toContain('customerId');
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it('accepts valid order', () => {
|
|
332
|
-
const result = OrderValidator.validate({
|
|
333
|
-
customerId: 'C1',
|
|
334
|
-
items: [{ productId: 'P1', quantity: 1 }]
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
expect(result.ok).toBe(true);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// ✓ DO: Test grouping logic (not lodash)
|
|
342
|
-
describe('OrderAnalytics', () => {
|
|
343
|
-
it('groups orders by customer', () => {
|
|
344
|
-
const orders = [
|
|
345
|
-
{ id: '1', customerId: 'C1' },
|
|
346
|
-
{ id: '2', customerId: 'C2' },
|
|
347
|
-
{ id: '3', customerId: 'C1' }
|
|
348
|
-
];
|
|
349
|
-
|
|
350
|
-
const grouped = OrderAnalytics.groupByCustomer(orders);
|
|
351
|
-
|
|
352
|
-
expect(grouped['C1']).toHaveLength(2);
|
|
353
|
-
expect(grouped['C2']).toHaveLength(1);
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
## Over-Testing (Too Many Tests)
|
|
359
|
-
|
|
360
|
-
Test everything eventually fails: changes break dozens of tests, even when behavior unchanged.
|
|
361
|
-
|
|
362
|
-
### Anti-Pattern: Testing Every Line
|
|
363
|
-
|
|
364
|
-
```typescript
|
|
365
|
-
// ❌ DON'T: Test every internal detail
|
|
366
|
-
class OrderService {
|
|
367
|
-
async createOrder(request: CreateOrderRequest): Promise<Result<Order>> {
|
|
368
|
-
// Test 1: validating format
|
|
369
|
-
if (!request.customerId) return Err('Missing customerId');
|
|
370
|
-
|
|
371
|
-
// Test 2: validating items
|
|
372
|
-
if (!request.items || request.items.length === 0) return Err('Empty items');
|
|
373
|
-
|
|
374
|
-
// Test 3: validating prices
|
|
375
|
-
for (const item of request.items) {
|
|
376
|
-
if (item.price < 0) return Err('Negative price');
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Test 4: validation result
|
|
380
|
-
const order = Order.fromRequest(request);
|
|
381
|
-
const validation = order.validate();
|
|
382
|
-
if (!validation.ok) return validation;
|
|
383
|
-
|
|
384
|
-
// Test 5: repo call
|
|
385
|
-
const record = order.toRecord();
|
|
386
|
-
|
|
387
|
-
// Test 6: repo result
|
|
388
|
-
const saved = await this.repo.save(record);
|
|
389
|
-
|
|
390
|
-
// Test 7: transformation
|
|
391
|
-
return Ok(Order.fromRecord(saved));
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
describe('OrderService.createOrder', () => {
|
|
396
|
-
it('returns error for missing customerId', async () => { /* ... */ });
|
|
397
|
-
it('returns error for empty items', async () => { /* ... */ });
|
|
398
|
-
it('returns error for negative price', async () => { /* ... */ });
|
|
399
|
-
it('validates order entity', async () => { /* ... */ });
|
|
400
|
-
it('calls repo save', async () => { /* ... */ });
|
|
401
|
-
it('transforms record to entity', async () => { /* ... */ });
|
|
402
|
-
it('returns Ok result', async () => { /* ... */ });
|
|
403
|
-
// 7 tests for 1 method - high maintenance
|
|
404
|
-
});
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
**Problem:**
|
|
408
|
-
- Every refactor breaks multiple tests
|
|
409
|
-
- Tests test implementation, not behavior
|
|
410
|
-
- Slow feedback loop
|
|
411
|
-
- Hard to see what actually matters
|
|
412
|
-
|
|
413
|
-
**Solution:** Test behavior, not implementation
|
|
414
|
-
|
|
415
|
-
```typescript
|
|
416
|
-
// ✓ DO: Test high-level behavior
|
|
417
|
-
describe('OrderService.createOrder', () => {
|
|
418
|
-
it('creates order for valid request', async () => {
|
|
419
|
-
const mockRepo = {
|
|
420
|
-
save: vi.fn().mockResolvedValue({ id: '123' })
|
|
421
|
-
};
|
|
422
|
-
const service = new OrderService(mockRepo);
|
|
423
|
-
|
|
424
|
-
const result = await service.createOrder({
|
|
425
|
-
customerId: 'C1',
|
|
426
|
-
items: [{ productId: 'P1', quantity: 1, price: 10 }]
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
expect(result.ok).toBe(true);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
it('rejects invalid order', async () => {
|
|
433
|
-
const mockRepo = { save: vi.fn() };
|
|
434
|
-
const service = new OrderService(mockRepo);
|
|
435
|
-
|
|
436
|
-
const result = await service.createOrder({
|
|
437
|
-
customerId: 'C1',
|
|
438
|
-
items: [] // Invalid
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
expect(result.ok).toBe(false);
|
|
442
|
-
expect(mockRepo.save).not.toHaveBeenCalled();
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// 2 tests covering all code paths
|
|
446
|
-
});
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
## Under-Testing (Too Few Tests)
|
|
450
|
-
|
|
451
|
-
Only testing happy path leaves bugs in error handling and edge cases.
|
|
452
|
-
|
|
453
|
-
### Anti-Pattern: Only Testing Happy Path
|
|
454
|
-
|
|
455
|
-
```typescript
|
|
456
|
-
// ❌ DON'T: Only test success case
|
|
457
|
-
describe('OrderService.createOrder', () => {
|
|
458
|
-
it('creates order', async () => {
|
|
459
|
-
const mockRepo = {
|
|
460
|
-
save: vi.fn().mockResolvedValue({ id: '123' })
|
|
461
|
-
};
|
|
462
|
-
const service = new OrderService(mockRepo);
|
|
463
|
-
|
|
464
|
-
const result = await service.createOrder({
|
|
465
|
-
customerId: 'C1',
|
|
466
|
-
items: [{ productId: 'P1', quantity: 1, price: 10 }]
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// Only tests success
|
|
470
|
-
expect(result.ok).toBe(true);
|
|
471
|
-
});
|
|
472
|
-
});
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
**Problem:**
|
|
476
|
-
- Error handling untested
|
|
477
|
-
- Edge cases not covered
|
|
478
|
-
- Production bugs from unhandled errors
|
|
479
|
-
|
|
480
|
-
**Solution:** Test both success and failure paths
|
|
481
|
-
|
|
482
|
-
```typescript
|
|
483
|
-
// ✓ DO: Test success and error paths
|
|
484
|
-
describe('OrderService.createOrder', () => {
|
|
485
|
-
it('creates order for valid request', async () => {
|
|
486
|
-
const mockRepo = {
|
|
487
|
-
save: vi.fn().mockResolvedValue({ id: '123' })
|
|
488
|
-
};
|
|
489
|
-
const service = new OrderService(mockRepo);
|
|
490
|
-
|
|
491
|
-
const result = await service.createOrder({
|
|
492
|
-
customerId: 'C1',
|
|
493
|
-
items: [{ productId: 'P1', quantity: 1, price: 10 }]
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
expect(result.ok).toBe(true);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
it('returns error for empty items', async () => {
|
|
500
|
-
const service = new OrderService(mockRepo);
|
|
501
|
-
|
|
502
|
-
const result = await service.createOrder({
|
|
503
|
-
customerId: 'C1',
|
|
504
|
-
items: []
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
expect(result.ok).toBe(false);
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
it('returns error for missing customerId', async () => {
|
|
511
|
-
const service = new OrderService(mockRepo);
|
|
512
|
-
|
|
513
|
-
const result = await service.createOrder({
|
|
514
|
-
customerId: '',
|
|
515
|
-
items: [{ productId: 'P1', quantity: 1, price: 10 }]
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
expect(result.ok).toBe(false);
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
it('handles repository errors gracefully', async () => {
|
|
522
|
-
const mockRepo = {
|
|
523
|
-
save: vi.fn().mockRejectedValue(new Error('DB error'))
|
|
524
|
-
};
|
|
525
|
-
const service = new OrderService(mockRepo);
|
|
526
|
-
|
|
527
|
-
const result = await service.createOrder({
|
|
528
|
-
customerId: 'C1',
|
|
529
|
-
items: [{ productId: 'P1', quantity: 1, price: 10 }]
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
expect(result.ok).toBe(false);
|
|
533
|
-
});
|
|
534
|
-
});
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
## Testing with Unstable Mocks
|
|
538
|
-
|
|
539
|
-
Mocks that change behavior make tests brittle and unreliable.
|
|
540
|
-
|
|
541
|
-
### Anti-Pattern: Using Complex, Unstable Mocks
|
|
542
|
-
|
|
543
|
-
```typescript
|
|
544
|
-
// ❌ DON'T: Create complex, coupled mocks
|
|
545
|
-
class OrderServiceTest {
|
|
546
|
-
createMockService() {
|
|
547
|
-
return {
|
|
548
|
-
createOrder: vi.fn(async (req) => {
|
|
549
|
-
// Mock coupled to implementation
|
|
550
|
-
if (!req.customerId) return Err('Missing');
|
|
551
|
-
if (!req.items || req.items.length === 0) return Err('Empty');
|
|
552
|
-
|
|
553
|
-
// Duplicates validation logic from real service
|
|
554
|
-
return Ok(new Order('123', req.customerId, req.items, 'pending', 35));
|
|
555
|
-
})
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// Test breaks if validation changes in real service
|
|
561
|
-
describe('OrderController', () => {
|
|
562
|
-
it('handles service error', async () => {
|
|
563
|
-
const mockService = createMockService();
|
|
564
|
-
const controller = new OrderController(mockService);
|
|
565
|
-
|
|
566
|
-
const result = await controller.createOrder({ customerId: '', items: [] });
|
|
567
|
-
|
|
568
|
-
// Brittle - depends on mock implementation
|
|
569
|
-
expect(result.status).toBe(400);
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
**Problem:**
|
|
575
|
-
- Mock duplicates validation logic
|
|
576
|
-
- Test breaks if validation logic changes
|
|
577
|
-
- Mock and real code diverge over time
|
|
578
|
-
|
|
579
|
-
**Solution:** Use simple, declarative mocks
|
|
580
|
-
|
|
581
|
-
```typescript
|
|
582
|
-
// ✓ DO: Use simple mocks with vi.fn()
|
|
583
|
-
describe('OrderController', () => {
|
|
584
|
-
it('handles service error', async () => {
|
|
585
|
-
const mockService = {
|
|
586
|
-
createOrder: vi.fn().mockResolvedValue(Err('Invalid order'))
|
|
587
|
-
};
|
|
588
|
-
const controller = new OrderController(mockService);
|
|
589
|
-
|
|
590
|
-
const result = await controller.createOrder({
|
|
591
|
-
customerId: 'C1',
|
|
592
|
-
items: [{ productId: 'P1', quantity: 1, price: 10 }]
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
expect(result.status).toBe(400);
|
|
596
|
-
expect(result.body.error).toBe('Invalid order');
|
|
597
|
-
});
|
|
598
|
-
});
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
## Summary: When to Skip Tests
|
|
602
|
-
|
|
603
|
-
Save effort by NOT testing:
|
|
604
|
-
|
|
605
|
-
| What | Why | Exception |
|
|
606
|
-
|------|-----|-----------|
|
|
607
|
-
| Getters/setters | No logic | Computed properties |
|
|
608
|
-
| Framework behavior | Already tested | Your integration with framework |
|
|
609
|
-
| Library behavior | Already tested | Your usage of library |
|
|
610
|
-
| Trivial mappings | No logic, type-safe | Mappings with transformation |
|
|
611
|
-
| Private methods | Access through public API | Can't isolate the logic |
|
|
612
|
-
| Internal details | Break on refactor | Public behavior only |
|
|
613
|
-
|
|
614
|
-
Focus testing effort on:
|
|
615
|
-
- Business logic and rules
|
|
616
|
-
- Validation and error handling
|
|
617
|
-
- Integration points and boundaries
|
|
618
|
-
- Public APIs and contracts
|