omgkit 2.2.0 → 2.3.1

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.
Files changed (60) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/skills/databases/database-management/SKILL.md +288 -0
  4. package/plugin/skills/databases/database-migration/SKILL.md +285 -0
  5. package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
  6. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  7. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  8. package/plugin/skills/databases/redis/SKILL.md +53 -860
  9. package/plugin/skills/databases/supabase/SKILL.md +283 -0
  10. package/plugin/skills/devops/aws/SKILL.md +68 -672
  11. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  12. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  13. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  14. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  15. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  16. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  17. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  18. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  19. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  20. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  21. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  23. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  26. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  27. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  28. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  29. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  30. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  31. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  32. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  33. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  34. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  35. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  36. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  37. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  38. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  39. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  40. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  41. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  42. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  43. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  44. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  45. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  46. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  47. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  48. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  49. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  50. package/plugin/skills/security/oauth/SKILL.md +80 -934
  51. package/plugin/skills/security/owasp/SKILL.md +78 -862
  52. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  53. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  54. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  55. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  56. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  57. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  58. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  59. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  60. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,712 +1,103 @@
1
1
  ---
2
- name: testing-anti-patterns
3
- description: Common testing anti-patterns to avoid and how to fix them for reliable, maintainable test suites
4
- category: methodology
5
- triggers:
6
- - testing anti-patterns
7
- - flaky tests
8
- - test smells
9
- - bad tests
10
- - test maintenance
11
- - unreliable tests
12
- - test code quality
2
+ name: avoiding-testing-anti-patterns
3
+ description: AI agent identifies and fixes common testing anti-patterns that lead to flaky, slow, or unmaintainable test suites. Use when reviewing tests, debugging test failures, or improving test quality.
13
4
  ---
14
5
 
15
- # Testing Anti-Patterns
6
+ # Avoiding Testing Anti-Patterns
16
7
 
17
- Identify and fix **common testing anti-patterns** that lead to flaky, slow, or unmaintainable test suites. This skill provides patterns to recognize problematic tests and techniques to refactor them into reliable, valuable tests.
8
+ ## Quick Start
18
9
 
19
- ## Purpose
20
-
21
- Build reliable, maintainable test suites:
22
-
23
- - Recognize testing anti-patterns quickly
24
- - Fix flaky tests that undermine confidence
25
- - Avoid tests that test implementation details
26
- - Eliminate slow tests that hurt development velocity
27
- - Remove tests that are hard to maintain
28
- - Build tests that provide real value
29
- - Create self-documenting test suites
10
+ 1. **Identify** - Recognize anti-pattern category (flaky, implementation, over-mocking)
11
+ 2. **Assess Severity** - Critical (fix now), High (fix soon), Medium (plan to fix)
12
+ 3. **Apply Fix** - Use proper async handling, test behavior not implementation
13
+ 4. **Verify** - Run tests in random order, ensure independence
14
+ 5. **Prevent** - Add test smell detection to CI
30
15
 
31
16
  ## Features
32
17
 
33
- ### 1. The Testing Anti-Pattern Catalog
34
-
35
- ```markdown
36
- ## Common Testing Anti-Patterns
37
-
38
- ┌─────────────────────────────────────────────────────────────────────────┐
39
- │ TESTING ANTI-PATTERN SEVERITY │
40
- ├─────────────────────────────────────────────────────────────────────────┤
41
- │ │
42
- │ 🔴 CRITICAL (Fix immediately) │
43
- │ ───────────────────────────── │
44
- │ • Flaky tests - Random failures destroy trust │
45
- │ • Testing implementation - Breaks on every refactor │
46
- │ • Hidden dependencies - Tests fail mysteriously │
47
- │ │
48
- │ 🟡 HIGH (Fix soon) │
49
- │ ───────────────── │
50
- │ • Slow tests - Hurt development velocity │
51
- │ • Test interdependence - Can't run tests in isolation │
52
- │ • Over-mocking - Tests pass but bugs slip through │
53
- │ │
54
- │ 🟠 MEDIUM (Plan to fix) │
55
- │ ─────────────────────── │
56
- │ • Poor naming - Tests don't document behavior │
57
- │ • Magic values - Unclear why values are expected │
58
- │ • Giant tests - Hard to understand and maintain │
59
- │ │
60
- │ 🔵 LOW (Fix when touching) │
61
- │ ───────────────────────── │
62
- │ • Commented tests - Remove or fix them │
63
- │ • Duplicate tests - Consolidate coverage │
64
- │ • Dead assertions - Remove or make meaningful │
65
- │ │
66
- └─────────────────────────────────────────────────────────────────────────┘
67
- ```
18
+ | Feature | Description | Guide |
19
+ |---------|-------------|-------|
20
+ | Flaky Tests | Random failures destroying trust | Use waitFor, not sleep; deterministic data |
21
+ | Implementation Testing | Breaks on every refactor | Test behavior through public interface |
22
+ | Over-Mocking | Tests pass but bugs slip through | Mock boundaries only, not your own code |
23
+ | Slow Tests | Hurt development velocity | Right test layer, shared setup, mock network |
24
+ | Test Interdependence | Can't run tests in isolation | Fresh state in beforeEach, no shared mutation |
25
+ | Poor Design | Hard to understand/maintain | Descriptive names, focused tests, clear values |
68
26
 
69
- ### 2. Flaky Tests
27
+ ## Common Patterns
70
28
 
71
29
  ```typescript
72
- /**
73
- * ANTI-PATTERN: Flaky Tests
74
- * Tests that sometimes pass, sometimes fail
75
- */
76
-
77
- // ❌ FLAKY: Timing-dependent test
78
- describe('FlakY: Timing Issues', () => {
79
- it('processes data after delay', async () => {
80
- startAsyncProcess();
81
-
82
- // PROBLEM: 100ms might not be enough on slow CI
83
- await sleep(100);
84
-
85
- expect(result).toBe('processed');
86
- });
87
- });
88
-
89
- // ✅ FIXED: Wait for condition, not time
90
- describe('Fixed: Proper async handling', () => {
91
- it('processes data after delay', async () => {
92
- startAsyncProcess();
93
-
94
- // Wait for actual condition
95
- await waitFor(() => {
96
- expect(result).toBe('processed');
97
- }, { timeout: 5000 });
98
- });
99
- });
100
-
101
- // ❌ FLAKY: Random data without determinism
102
- describe('Flaky: Random Data', () => {
103
- it('handles user data', () => {
104
- const user = {
105
- id: Math.random().toString(),
106
- name: faker.name.fullName()
107
- };
108
-
109
- const result = processUser(user);
110
-
111
- // PROBLEM: Can't know expected result
112
- expect(result).toBeDefined();
113
- });
114
- });
115
-
116
- // ✅ FIXED: Deterministic test data
117
- describe('Fixed: Deterministic Data', () => {
118
- it('handles user data', () => {
119
- const user = {
120
- id: 'user-123',
121
- name: 'John Doe'
122
- };
123
-
124
- const result = processUser(user);
125
-
126
- expect(result).toEqual({
127
- id: 'user-123',
128
- displayName: 'John Doe',
129
- slug: 'john-doe'
130
- });
131
- });
132
- });
30
+ // FLAKY: Timing-dependent
31
+ await sleep(100); // May not be enough
32
+ expect(result).toBe('processed');
133
33
 
134
- // ❌ FLAKY: Order-dependent tests
135
- describe('Flaky: Order Dependent', () => {
136
- let sharedState = [];
34
+ // FIXED: Wait for condition
35
+ await waitFor(() => {
36
+ expect(result).toBe('processed');
37
+ }, { timeout: 5000 });
137
38
 
138
- it('adds item', () => {
139
- sharedState.push('item1');
140
- expect(sharedState).toHaveLength(1);
141
- });
39
+ // FLAKY: Shared state between tests
40
+ let sharedState = [];
41
+ it('test1', () => { sharedState.push('a'); });
42
+ it('test2', () => { expect(sharedState).toHaveLength(1); }); // Order-dependent!
142
43
 
143
- it('has two items', () => {
144
- // PROBLEM: Depends on previous test running first
145
- sharedState.push('item2');
146
- expect(sharedState).toHaveLength(2);
147
- });
148
- });
44
+ // FIXED: Fresh state each test
45
+ beforeEach(() => { sharedState = []; });
149
46
 
150
- // ✅ FIXED: Independent tests
151
- describe('Fixed: Independent Tests', () => {
152
- let sharedState: string[];
47
+ // IMPLEMENTATION: Testing private state
48
+ expect(counter._count).toBe(1);
153
49
 
154
- beforeEach(() => {
155
- sharedState = []; // Fresh state each test
156
- });
50
+ // BEHAVIOR: Testing public interface
51
+ expect(counter.getValue()).toBe(1);
157
52
 
158
- it('adds first item', () => {
159
- sharedState.push('item1');
160
- expect(sharedState).toHaveLength(1);
161
- });
53
+ // OVER-MOCKING: Everything mocked
54
+ const mockDb = { save: jest.fn() };
55
+ const mockPayment = { charge: jest.fn() };
56
+ // Only testing that mocks were called
162
57
 
163
- it('adds second item', () => {
164
- sharedState.push('item2');
165
- expect(sharedState).toHaveLength(1); // Independent!
166
- });
167
- });
58
+ // FIXED: Mock boundaries only
59
+ const testDb = await createTestDatabase(); // Real
60
+ const mockPayment = createMockPaymentProvider(); // External only
168
61
  ```
169
62
 
170
- ### 3. Testing Implementation Details
171
-
172
- ```typescript
173
- /**
174
- * ANTI-PATTERN: Testing Implementation
175
- * Tests break when implementation changes, even if behavior is correct
176
- */
177
-
178
- // ❌ BAD: Testing internal state
179
- describe('Anti-pattern: Internal State', () => {
180
- it('sets internal counter', () => {
181
- const counter = new Counter();
182
- counter.increment();
183
-
184
- // PROBLEM: Testing private implementation
185
- expect(counter._count).toBe(1);
186
- });
187
- });
188
-
189
- // ✅ GOOD: Testing behavior
190
- describe('Pattern: Test Behavior', () => {
191
- it('increments and returns count', () => {
192
- const counter = new Counter();
193
-
194
- expect(counter.increment()).toBe(1);
195
- expect(counter.getValue()).toBe(1);
196
- });
197
- });
198
-
199
- // ❌ BAD: Testing function calls
200
- describe('Anti-pattern: Spy Everything', () => {
201
- it('calls internal methods', () => {
202
- const service = new UserService();
203
- const validateSpy = vi.spyOn(service, 'validateEmail');
204
- const hashSpy = vi.spyOn(service, 'hashPassword');
205
-
206
- service.createUser({ email: 'test@test.com', password: '123' });
207
-
208
- // PROBLEM: Tightly coupled to implementation
209
- expect(validateSpy).toHaveBeenCalledWith('test@test.com');
210
- expect(hashSpy).toHaveBeenCalledWith('123');
211
- });
212
- });
213
-
214
- // ✅ GOOD: Testing outcomes
215
- describe('Pattern: Test Outcomes', () => {
216
- it('creates user with hashed password', async () => {
217
- const service = new UserService();
218
-
219
- const user = await service.createUser({
220
- email: 'test@test.com',
221
- password: '123'
222
- });
223
-
224
- // Test the result, not how we got there
225
- expect(user.email).toBe('test@test.com');
226
- expect(user.password).not.toBe('123'); // Hashed
227
- expect(user.password).toMatch(/^\$2[aby]?\$/); // bcrypt format
228
- });
229
- });
230
-
231
- // ❌ BAD: Testing component internals (React)
232
- describe('Anti-pattern: Component Internals', () => {
233
- it('sets state on click', () => {
234
- const wrapper = shallow(<Counter />);
235
-
236
- wrapper.find('button').simulate('click');
237
-
238
- // PROBLEM: Testing React implementation
239
- expect(wrapper.state('count')).toBe(1);
240
- });
241
- });
242
-
243
- // ✅ GOOD: Testing user-facing behavior
244
- describe('Pattern: User Behavior', () => {
245
- it('shows incremented count on click', () => {
246
- render(<Counter />);
247
-
248
- const button = screen.getByRole('button', { name: /increment/i });
249
- fireEvent.click(button);
250
-
251
- // Test what user sees
252
- expect(screen.getByText('Count: 1')).toBeInTheDocument();
253
- });
254
- });
255
63
  ```
256
-
257
- ### 4. Over-Mocking
258
-
259
- ```typescript
260
- /**
261
- * ANTI-PATTERN: Over-Mocking
262
- * Mocking so much that tests pass but bugs slip through
263
- */
264
-
265
- // BAD: Everything is mocked
266
- describe('Anti-pattern: Mock Everything', () => {
267
- it('processes order', async () => {
268
- const mockDb = { save: vi.fn() };
269
- const mockPayment = { charge: vi.fn().mockResolvedValue({ success: true }) };
270
- const mockEmail = { send: vi.fn() };
271
- const mockInventory = { reserve: vi.fn().mockResolvedValue(true) };
272
- const mockShipping = { calculate: vi.fn().mockReturnValue(5.99) };
273
-
274
- const service = new OrderService({
275
- db: mockDb,
276
- payment: mockPayment,
277
- email: mockEmail,
278
- inventory: mockInventory,
279
- shipping: mockShipping
280
- });
281
-
282
- await service.processOrder(mockOrder);
283
-
284
- // PROBLEM: We're only testing that our mocks were called
285
- // The actual integration could be completely broken
286
- expect(mockPayment.charge).toHaveBeenCalled();
287
- expect(mockDb.save).toHaveBeenCalled();
288
- });
289
- });
290
-
291
- // ✅ GOOD: Mock boundaries, not internals
292
- describe('Pattern: Mock External Boundaries', () => {
293
- let service: OrderService;
294
- let testDb: TestDatabase;
295
-
296
- beforeAll(async () => {
297
- testDb = await createTestDatabase();
298
- });
299
-
300
- beforeEach(async () => {
301
- // Use real database, mock only external services
302
- service = new OrderService({
303
- db: testDb,
304
- payment: createMockPaymentProvider(), // External API
305
- email: createMockEmailProvider(), // External service
306
- inventory: new InventoryService(testDb), // Real, uses test DB
307
- shipping: new ShippingCalculator() // Real, pure logic
308
- });
309
- });
310
-
311
- it('processes order end-to-end', async () => {
312
- const order = await service.processOrder({
313
- items: [{ productId: 'prod-1', quantity: 2 }],
314
- customer: testCustomer
315
- });
316
-
317
- // Real assertions on real behavior
318
- expect(order.status).toBe('confirmed');
319
- expect(order.total).toBe(29.98);
320
-
321
- // Verify real database state
322
- const dbOrder = await testDb.orders.findById(order.id);
323
- expect(dbOrder.status).toBe('confirmed');
324
- });
325
- });
326
-
327
- // The Mock Boundary Principle
328
- const mockBoundaryGuidelines = {
329
- mock: [
330
- 'External APIs (payment, email, SMS)',
331
- 'Third-party services',
332
- 'System clock (for determinism)',
333
- 'Random number generators',
334
- 'Network requests'
335
- ],
336
- dontMock: [
337
- 'Your own code (usually)',
338
- 'Pure functions',
339
- 'Data transformations',
340
- 'Business logic',
341
- 'Internal services (use test doubles with real behavior)'
342
- ]
343
- };
344
- ```
345
-
346
- ### 5. Slow Tests
347
-
348
- ```typescript
349
- /**
350
- * ANTI-PATTERN: Slow Tests
351
- * Tests that take too long, hurting development velocity
352
- */
353
-
354
- // ❌ SLOW: Unnecessary database for unit test
355
- describe('Anti-pattern: Wrong Layer', () => {
356
- it('validates email format', async () => {
357
- // PROBLEM: Spinning up DB just to test string validation
358
- const db = await createDatabase();
359
- const service = new UserService(db);
360
-
361
- const result = service.validateEmail('invalid');
362
-
363
- expect(result).toBe(false);
364
- await db.close();
365
- });
366
- });
367
-
368
- // ✅ FAST: Test pure logic directly
369
- describe('Pattern: Right Layer', () => {
370
- it('validates email format', () => {
371
- expect(validateEmail('invalid')).toBe(false);
372
- expect(validateEmail('valid@example.com')).toBe(true);
373
- });
374
- });
375
-
376
- // ❌ SLOW: Redundant setup
377
- describe('Anti-pattern: Heavy Setup', () => {
378
- it('test 1', async () => {
379
- const db = await createDatabase();
380
- await seedUsers(100);
381
- await seedProducts(1000);
382
- await seedOrders(500);
383
-
384
- const result = await getUser('user-1');
385
- expect(result.name).toBe('User 1');
386
- });
387
-
388
- it('test 2', async () => {
389
- // Same heavy setup repeated!
390
- const db = await createDatabase();
391
- await seedUsers(100);
392
- await seedProducts(1000);
393
- await seedOrders(500);
394
-
395
- const result = await getUser('user-2');
396
- expect(result.name).toBe('User 2');
397
- });
398
- });
399
-
400
- // ✅ FAST: Shared setup, minimal data
401
- describe('Pattern: Efficient Setup', () => {
402
- let db: TestDatabase;
403
-
404
- beforeAll(async () => {
405
- db = await createDatabase();
406
- // Seed only what's needed for this suite
407
- await seedUsers(3);
408
- });
409
-
410
- afterAll(async () => {
411
- await db.close();
412
- });
413
-
414
- it('gets user 1', async () => {
415
- const result = await getUser('user-1');
416
- expect(result.name).toBe('User 1');
417
- });
418
-
419
- it('gets user 2', async () => {
420
- const result = await getUser('user-2');
421
- expect(result.name).toBe('User 2');
422
- });
423
- });
424
-
425
- // ❌ SLOW: Real network in unit tests
426
- describe('Anti-pattern: Real Network', () => {
427
- it('fetches user from API', async () => {
428
- // PROBLEM: Hits real API, slow and flaky
429
- const response = await fetch('https://api.example.com/users/1');
430
- const user = await response.json();
431
-
432
- expect(user.name).toBe('John');
433
- });
434
- });
435
-
436
- // ✅ FAST: Mock network
437
- describe('Pattern: Mock Network', () => {
438
- beforeEach(() => {
439
- fetchMock.mockResponse(JSON.stringify({ name: 'John' }));
440
- });
441
-
442
- it('fetches user from API', async () => {
443
- const user = await fetchUser(1);
444
- expect(user.name).toBe('John');
445
- });
446
- });
447
- ```
448
-
449
- ### 6. Poor Test Design
450
-
451
- ```typescript
452
- /**
453
- * ANTI-PATTERN: Poor Test Design
454
- * Tests that are hard to understand or maintain
455
- */
456
-
457
- // ❌ BAD: Giant test with many assertions
458
- describe('Anti-pattern: Giant Test', () => {
459
- it('does everything', async () => {
460
- const service = new OrderService();
461
-
462
- // Create user
463
- const user = await service.createUser({ name: 'John' });
464
- expect(user.id).toBeDefined();
465
-
466
- // Add payment method
467
- await service.addPaymentMethod(user.id, { type: 'card' });
468
- expect(user.paymentMethods).toHaveLength(1);
469
-
470
- // Create order
471
- const order = await service.createOrder(user.id, [{ id: 'p1' }]);
472
- expect(order.status).toBe('pending');
473
-
474
- // Process payment
475
- await service.processPayment(order.id);
476
- expect(order.status).toBe('paid');
477
-
478
- // Ship order
479
- await service.shipOrder(order.id);
480
- expect(order.status).toBe('shipped');
481
-
482
- // ... 20 more steps
483
- // PROBLEM: One failure, no idea where
484
- });
485
- });
486
-
487
- // ✅ GOOD: Focused tests
488
- describe('Pattern: Focused Tests', () => {
489
- describe('Order Creation', () => {
490
- it('creates pending order from cart items', async () => {
491
- const order = await service.createOrder(user.id, cartItems);
492
-
493
- expect(order.status).toBe('pending');
494
- expect(order.items).toEqual(cartItems);
495
- });
496
- });
497
-
498
- describe('Payment Processing', () => {
499
- it('marks order as paid after successful charge', async () => {
500
- const order = await createPendingOrder();
501
-
502
- await service.processPayment(order.id);
503
-
504
- expect(await getOrder(order.id).status).toBe('paid');
505
- });
506
-
507
- it('keeps order pending if charge fails', async () => {
508
- const order = await createPendingOrder();
509
- mockPayment.failNextCharge();
510
-
511
- await expect(
512
- service.processPayment(order.id)
513
- ).rejects.toThrow(PaymentError);
514
-
515
- expect(await getOrder(order.id).status).toBe('pending');
516
- });
517
- });
518
- });
519
-
520
- // ❌ BAD: Magic values
521
- describe('Anti-pattern: Magic Values', () => {
522
- it('calculates discount', () => {
523
- // PROBLEM: Why 150? Why 15?
524
- expect(calculateDiscount(150)).toBe(15);
525
- expect(calculateDiscount(99)).toBe(0);
526
- });
527
- });
528
-
529
- // ✅ GOOD: Self-documenting values
530
- describe('Pattern: Clear Intent', () => {
531
- it('applies 10% discount for orders over $100', () => {
532
- const DISCOUNT_THRESHOLD = 100;
533
- const DISCOUNT_RATE = 0.10;
534
-
535
- const orderAboveThreshold = 150;
536
- const orderBelowThreshold = 99;
537
-
538
- expect(calculateDiscount(orderAboveThreshold))
539
- .toBe(orderAboveThreshold * DISCOUNT_RATE);
540
-
541
- expect(calculateDiscount(orderBelowThreshold))
542
- .toBe(0);
543
- });
544
- });
545
-
546
- // ❌ BAD: Unclear test names
547
- describe('Anti-pattern: Bad Names', () => {
548
- it('test1', () => { /* ... */ });
549
- it('should work', () => { /* ... */ });
550
- it('handles edge case', () => { /* ... */ });
551
- });
552
-
553
- // ✅ GOOD: Behavior-describing names
554
- describe('Pattern: Descriptive Names', () => {
555
- it('returns empty array when no products match filter', () => { /* ... */ });
556
- it('throws InvalidEmailError for malformed email addresses', () => { /* ... */ });
557
- it('retries up to 3 times before failing', () => { /* ... */ });
558
- });
559
- ```
560
-
561
- ### 7. Test Smells Detection
562
-
563
- ```typescript
564
- /**
565
- * Automated test smell detection
566
- */
567
-
568
- interface TestSmell {
569
- type: string;
570
- severity: 'critical' | 'high' | 'medium' | 'low';
571
- pattern: RegExp;
572
- fix: string;
573
- }
574
-
575
- const testSmells: TestSmell[] = [
576
- {
577
- type: 'sleep-in-test',
578
- severity: 'critical',
579
- pattern: /await\s+sleep\s*\(/,
580
- fix: 'Use waitFor or proper async handling'
581
- },
582
- {
583
- type: 'implementation-testing',
584
- severity: 'high',
585
- pattern: /\._\w+|\.state\(|\.instance\(\)/,
586
- fix: 'Test behavior through public interface'
587
- },
588
- {
589
- type: 'broad-assertion',
590
- severity: 'medium',
591
- pattern: /expect\([^)]+\)\.toBeDefined\(\)/,
592
- fix: 'Use specific assertions'
593
- },
594
- {
595
- type: 'magic-number',
596
- severity: 'medium',
597
- pattern: /expect\([^)]+\)\.toBe\(\d{3,}\)/,
598
- fix: 'Use named constants'
599
- },
600
- {
601
- type: 'commented-test',
602
- severity: 'low',
603
- pattern: /\/\/\s*it\(|\/\*[\s\S]*it\(/,
604
- fix: 'Remove or fix commented tests'
605
- }
606
- ];
607
-
608
- function detectTestSmells(testCode: string): DetectedSmell[] {
609
- const smells: DetectedSmell[] = [];
610
-
611
- for (const smell of testSmells) {
612
- const matches = testCode.matchAll(new RegExp(smell.pattern, 'g'));
613
- for (const match of matches) {
614
- smells.push({
615
- ...smell,
616
- location: match.index,
617
- snippet: match[0]
618
- });
619
- }
620
- }
621
-
622
- return smells;
623
- }
624
- ```
625
-
626
- ## Use Cases
627
-
628
- ### Refactoring a Flaky Test Suite
629
-
630
- ```typescript
631
- // Before: Flaky, slow, hard to maintain
632
- describe('OrderService (before)', () => {
633
- it('processes order', async () => {
634
- const db = await connectToRealDatabase();
635
- const order = await createOrder(db);
636
-
637
- // Flaky: timing
638
- await sleep(500);
639
-
640
- // Implementation detail
641
- expect(order._internalState).toBe('processing');
642
-
643
- // Magic value
644
- expect(order.total).toBe(127.45);
645
- });
646
- });
647
-
648
- // After: Reliable, fast, clear
649
- describe('OrderService (after)', () => {
650
- let testDb: TestDatabase;
651
- let service: OrderService;
652
-
653
- beforeAll(async () => {
654
- testDb = await createTestDatabase();
655
- service = new OrderService(testDb);
656
- });
657
-
658
- it('calculates total from items', async () => {
659
- const items = [
660
- createTestItem({ price: 99.99 }),
661
- createTestItem({ price: 27.46 })
662
- ];
663
-
664
- const order = await service.createOrder(items);
665
-
666
- // Clear expectation
667
- expect(order.total).toBe(127.45);
668
- });
669
-
670
- it('transitions to processing after payment', async () => {
671
- const order = await createPendingOrder();
672
-
673
- await service.processPayment(order.id);
674
-
675
- // Test behavior, not implementation
676
- await waitFor(() => {
677
- expect(service.getOrderStatus(order.id)).toBe('processing');
678
- });
679
- });
680
- });
64
+ # Anti-Pattern Severity Guide
65
+ CRITICAL (fix immediately):
66
+ - Flaky tests - Random failures destroy trust
67
+ - Testing implementation - Breaks on every refactor
68
+ - Hidden dependencies - Tests fail mysteriously
69
+
70
+ HIGH (fix soon):
71
+ - Slow tests - Hurt development velocity
72
+ - Test interdependence - Can't run in isolation
73
+ - Over-mocking - Tests pass but bugs slip through
74
+
75
+ MEDIUM (plan to fix):
76
+ - Poor naming - Tests don't document behavior
77
+ - Magic values - Unclear expected values
78
+ - Giant tests - Hard to understand
79
+
80
+ LOW (fix when touching):
81
+ - Commented tests - Remove or fix
82
+ - Duplicate tests - Consolidate
681
83
  ```
682
84
 
683
85
  ## Best Practices
684
86
 
685
- ### Do's
686
-
687
- - **Test behavior, not implementation** - what it does, not how
688
- - **Use factories for test data** - consistent, typed data
689
- - **Write descriptive test names** - document the behavior
690
- - **Keep tests independent** - no shared mutable state
691
- - **Mock only external boundaries** - APIs, not your code
692
- - **Use proper async patterns** - waitFor, not sleep
693
- - **Make assertions specific** - not just toBeDefined
694
- - **Run tests in random order** - catch hidden dependencies
695
-
696
- ### Don'ts
697
-
698
- - Don't use sleep/setTimeout in tests
699
- - Don't access private properties
700
- - Don't share state between tests
701
- - Don't mock everything
702
- - Don't write 1000-line test files
703
- - Don't use real network/databases in unit tests
704
- - Don't ignore flaky tests
705
- - Don't comment out failing tests
706
-
707
- ## References
708
-
709
- - [Testing Library Guiding Principles](https://testing-library.com/docs/guiding-principles)
710
- - [Test Desiderata - Kent Beck](https://medium.com/@kentbeck_7670/test-desiderata-94150638a4b3)
711
- - [xUnit Test Patterns](http://xunitpatterns.com/)
712
- - [Growing Object-Oriented Software, Guided by Tests](http://www.growing-object-oriented-software.com/)
87
+ | Do | Avoid |
88
+ |----|-------|
89
+ | Test behavior, not implementation | Accessing private properties |
90
+ | Use factories for test data | Random/inconsistent test data |
91
+ | Write descriptive test names | Generic names like "test1", "should work" |
92
+ | Keep tests independent | Shared mutable state between tests |
93
+ | Mock only external boundaries | Mocking your own code extensively |
94
+ | Use waitFor, not sleep | setTimeout/sleep in tests |
95
+ | Make assertions specific | toBeDefined() for everything |
96
+ | Run tests in random order | Assuming test execution order |
97
+
98
+ ## Related Skills
99
+
100
+ - `developing-test-driven` - TDD with proper patterns
101
+ - `testing-with-vitest` - Vitest testing framework
102
+ - `testing-with-playwright` - E2E testing patterns
103
+ - `debugging-systematically` - Debug flaky test failures