opencode-metis 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/README.md +140 -0
- package/dist/cli.cjs +63 -0
- package/dist/mcp-server.cjs +51 -0
- package/dist/plugin.cjs +4 -0
- package/dist/worker.cjs +224 -0
- package/opencode/agent/the-analyst/feature-prioritization.md +66 -0
- package/opencode/agent/the-analyst/market-research.md +77 -0
- package/opencode/agent/the-analyst/project-coordination.md +81 -0
- package/opencode/agent/the-analyst/requirements-analysis.md +77 -0
- package/opencode/agent/the-architect/compatibility-review.md +138 -0
- package/opencode/agent/the-architect/complexity-review.md +137 -0
- package/opencode/agent/the-architect/quality-review.md +67 -0
- package/opencode/agent/the-architect/security-review.md +127 -0
- package/opencode/agent/the-architect/system-architecture.md +119 -0
- package/opencode/agent/the-architect/system-documentation.md +83 -0
- package/opencode/agent/the-architect/technology-research.md +85 -0
- package/opencode/agent/the-chief.md +79 -0
- package/opencode/agent/the-designer/accessibility-implementation.md +101 -0
- package/opencode/agent/the-designer/design-foundation.md +74 -0
- package/opencode/agent/the-designer/interaction-architecture.md +75 -0
- package/opencode/agent/the-designer/user-research.md +70 -0
- package/opencode/agent/the-meta-agent.md +155 -0
- package/opencode/agent/the-platform-engineer/ci-cd-pipelines.md +109 -0
- package/opencode/agent/the-platform-engineer/containerization.md +106 -0
- package/opencode/agent/the-platform-engineer/data-architecture.md +81 -0
- package/opencode/agent/the-platform-engineer/dependency-review.md +144 -0
- package/opencode/agent/the-platform-engineer/deployment-automation.md +81 -0
- package/opencode/agent/the-platform-engineer/infrastructure-as-code.md +107 -0
- package/opencode/agent/the-platform-engineer/performance-tuning.md +82 -0
- package/opencode/agent/the-platform-engineer/pipeline-engineering.md +81 -0
- package/opencode/agent/the-platform-engineer/production-monitoring.md +105 -0
- package/opencode/agent/the-qa-engineer/exploratory-testing.md +66 -0
- package/opencode/agent/the-qa-engineer/performance-testing.md +81 -0
- package/opencode/agent/the-qa-engineer/quality-assurance.md +77 -0
- package/opencode/agent/the-qa-engineer/test-execution.md +66 -0
- package/opencode/agent/the-software-engineer/api-development.md +78 -0
- package/opencode/agent/the-software-engineer/component-development.md +79 -0
- package/opencode/agent/the-software-engineer/concurrency-review.md +141 -0
- package/opencode/agent/the-software-engineer/domain-modeling.md +66 -0
- package/opencode/agent/the-software-engineer/performance-optimization.md +113 -0
- package/opencode/command/analyze.md +149 -0
- package/opencode/command/constitution.md +178 -0
- package/opencode/command/debug.md +194 -0
- package/opencode/command/document.md +178 -0
- package/opencode/command/implement.md +225 -0
- package/opencode/command/refactor.md +207 -0
- package/opencode/command/review.md +229 -0
- package/opencode/command/simplify.md +267 -0
- package/opencode/command/specify.md +191 -0
- package/opencode/command/validate.md +224 -0
- package/opencode/skill/accessibility-design/SKILL.md +566 -0
- package/opencode/skill/accessibility-design/checklists/wcag-checklist.md +435 -0
- package/opencode/skill/agent-coordination/SKILL.md +224 -0
- package/opencode/skill/api-contract-design/SKILL.md +550 -0
- package/opencode/skill/api-contract-design/templates/graphql-schema-template.md +818 -0
- package/opencode/skill/api-contract-design/templates/rest-api-template.md +417 -0
- package/opencode/skill/architecture-design/SKILL.md +160 -0
- package/opencode/skill/architecture-design/examples/architecture-examples.md +170 -0
- package/opencode/skill/architecture-design/template.md +749 -0
- package/opencode/skill/architecture-design/validation.md +99 -0
- package/opencode/skill/architecture-selection/SKILL.md +522 -0
- package/opencode/skill/architecture-selection/examples/adrs/001-example-adr.md +71 -0
- package/opencode/skill/architecture-selection/examples/architecture-patterns.md +239 -0
- package/opencode/skill/bug-diagnosis/SKILL.md +235 -0
- package/opencode/skill/code-quality-review/SKILL.md +337 -0
- package/opencode/skill/code-quality-review/examples/anti-patterns.md +629 -0
- package/opencode/skill/code-quality-review/reference.md +322 -0
- package/opencode/skill/code-review/SKILL.md +363 -0
- package/opencode/skill/code-review/reference.md +450 -0
- package/opencode/skill/codebase-analysis/SKILL.md +139 -0
- package/opencode/skill/codebase-navigation/SKILL.md +227 -0
- package/opencode/skill/codebase-navigation/examples/exploration-patterns.md +263 -0
- package/opencode/skill/coding-conventions/SKILL.md +178 -0
- package/opencode/skill/coding-conventions/checklists/accessibility-checklist.md +176 -0
- package/opencode/skill/coding-conventions/checklists/performance-checklist.md +154 -0
- package/opencode/skill/coding-conventions/checklists/security-checklist.md +127 -0
- package/opencode/skill/constitution-validation/SKILL.md +315 -0
- package/opencode/skill/constitution-validation/examples/CONSTITUTION.md +202 -0
- package/opencode/skill/constitution-validation/reference/rule-patterns.md +328 -0
- package/opencode/skill/constitution-validation/template.md +115 -0
- package/opencode/skill/context-preservation/SKILL.md +445 -0
- package/opencode/skill/data-modeling/SKILL.md +385 -0
- package/opencode/skill/data-modeling/templates/schema-design-template.md +268 -0
- package/opencode/skill/deployment-pipeline-design/SKILL.md +579 -0
- package/opencode/skill/deployment-pipeline-design/templates/pipeline-template.md +633 -0
- package/opencode/skill/documentation-extraction/SKILL.md +259 -0
- package/opencode/skill/documentation-sync/SKILL.md +431 -0
- package/opencode/skill/domain-driven-design/SKILL.md +509 -0
- package/opencode/skill/domain-driven-design/examples/ddd-patterns.md +688 -0
- package/opencode/skill/domain-driven-design/reference.md +465 -0
- package/opencode/skill/drift-detection/SKILL.md +383 -0
- package/opencode/skill/drift-detection/reference.md +340 -0
- package/opencode/skill/error-recovery/SKILL.md +162 -0
- package/opencode/skill/error-recovery/examples/error-patterns.md +484 -0
- package/opencode/skill/feature-prioritization/SKILL.md +419 -0
- package/opencode/skill/feature-prioritization/examples/rice-template.md +139 -0
- package/opencode/skill/feature-prioritization/reference.md +256 -0
- package/opencode/skill/git-workflow/SKILL.md +453 -0
- package/opencode/skill/implementation-planning/SKILL.md +215 -0
- package/opencode/skill/implementation-planning/examples/phase-examples.md +217 -0
- package/opencode/skill/implementation-planning/template.md +220 -0
- package/opencode/skill/implementation-planning/validation.md +88 -0
- package/opencode/skill/implementation-verification/SKILL.md +272 -0
- package/opencode/skill/knowledge-capture/SKILL.md +265 -0
- package/opencode/skill/knowledge-capture/reference/knowledge-capture.md +402 -0
- package/opencode/skill/knowledge-capture/reference.md +444 -0
- package/opencode/skill/knowledge-capture/templates/domain-template.md +325 -0
- package/opencode/skill/knowledge-capture/templates/interface-template.md +255 -0
- package/opencode/skill/knowledge-capture/templates/pattern-template.md +144 -0
- package/opencode/skill/observability-design/SKILL.md +291 -0
- package/opencode/skill/observability-design/references/monitoring-patterns.md +461 -0
- package/opencode/skill/pattern-detection/SKILL.md +171 -0
- package/opencode/skill/pattern-detection/examples/common-patterns.md +359 -0
- package/opencode/skill/performance-analysis/SKILL.md +266 -0
- package/opencode/skill/performance-analysis/references/profiling-tools.md +499 -0
- package/opencode/skill/requirements-analysis/SKILL.md +139 -0
- package/opencode/skill/requirements-analysis/examples/good-prd.md +66 -0
- package/opencode/skill/requirements-analysis/template.md +177 -0
- package/opencode/skill/requirements-analysis/validation.md +69 -0
- package/opencode/skill/requirements-elicitation/SKILL.md +518 -0
- package/opencode/skill/requirements-elicitation/examples/interview-questions.md +226 -0
- package/opencode/skill/requirements-elicitation/examples/user-stories.md +414 -0
- package/opencode/skill/safe-refactoring/SKILL.md +312 -0
- package/opencode/skill/safe-refactoring/reference/code-smells.md +347 -0
- package/opencode/skill/security-assessment/SKILL.md +421 -0
- package/opencode/skill/security-assessment/checklists/security-review-checklist.md +285 -0
- package/opencode/skill/specification-management/SKILL.md +143 -0
- package/opencode/skill/specification-management/readme-template.md +32 -0
- package/opencode/skill/specification-management/reference.md +115 -0
- package/opencode/skill/specification-management/spec.py +229 -0
- package/opencode/skill/specification-validation/SKILL.md +397 -0
- package/opencode/skill/specification-validation/reference/3cs-framework.md +306 -0
- package/opencode/skill/specification-validation/reference/ambiguity-detection.md +132 -0
- package/opencode/skill/specification-validation/reference/constitution-validation.md +301 -0
- package/opencode/skill/specification-validation/reference/drift-detection.md +383 -0
- package/opencode/skill/task-delegation/SKILL.md +607 -0
- package/opencode/skill/task-delegation/examples/file-coordination.md +495 -0
- package/opencode/skill/task-delegation/examples/parallel-research.md +337 -0
- package/opencode/skill/task-delegation/examples/sequential-build.md +504 -0
- package/opencode/skill/task-delegation/reference.md +825 -0
- package/opencode/skill/tech-stack-detection/SKILL.md +89 -0
- package/opencode/skill/tech-stack-detection/references/framework-signatures.md +598 -0
- package/opencode/skill/technical-writing/SKILL.md +190 -0
- package/opencode/skill/technical-writing/templates/adr-template.md +205 -0
- package/opencode/skill/technical-writing/templates/system-doc-template.md +380 -0
- package/opencode/skill/test-design/SKILL.md +464 -0
- package/opencode/skill/test-design/examples/test-pyramid.md +724 -0
- package/opencode/skill/testing/SKILL.md +213 -0
- package/opencode/skill/testing/examples/test-pyramid.md +724 -0
- package/opencode/skill/user-insight-synthesis/SKILL.md +576 -0
- package/opencode/skill/user-insight-synthesis/templates/research-plan-template.md +217 -0
- package/opencode/skill/user-research/SKILL.md +508 -0
- package/opencode/skill/user-research/examples/interview-questions.md +265 -0
- package/opencode/skill/user-research/examples/personas.md +267 -0
- package/opencode/skill/vibe-security/SKILL.md +654 -0
- package/package.json +45 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-design
|
|
3
|
+
description: Apply test pyramid principles, coverage targets, and framework-specific patterns. Use when designing test suites, reviewing test coverage, or implementing tests. Covers Jest, Pytest, and common testing frameworks with naming conventions and organization patterns.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: opencode
|
|
6
|
+
metadata:
|
|
7
|
+
category: development
|
|
8
|
+
version: "1.0"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Testing Strategies
|
|
12
|
+
|
|
13
|
+
Roleplay as a development specialist that provides comprehensive testing methodology including test pyramid structure, coverage targets, framework-specific patterns, and test organization conventions.
|
|
14
|
+
|
|
15
|
+
TestDesign {
|
|
16
|
+
Activation {
|
|
17
|
+
When designing test suites for new features or projects
|
|
18
|
+
When reviewing test coverage and identifying gaps
|
|
19
|
+
When implementing unit, integration, or E2E tests
|
|
20
|
+
When establishing test naming and organization conventions
|
|
21
|
+
When selecting appropriate testing frameworks
|
|
22
|
+
When planning test automation strategies
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
Constraints {
|
|
26
|
+
Test behavior, not implementation
|
|
27
|
+
One assertion per behavior (multiple related assertions acceptable)
|
|
28
|
+
Tests must be independent and not share mutable state
|
|
29
|
+
Run tests before committing; never commit failing tests
|
|
30
|
+
Keep unit tests under 100ms execution time
|
|
31
|
+
Delete flaky tests or fix them immediately
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
TestPyramid {
|
|
35
|
+
```
|
|
36
|
+
/\
|
|
37
|
+
/ \ E2E Tests (5-10%)
|
|
38
|
+
/----\ - Critical user journeys
|
|
39
|
+
/ \ - Slow, expensive, flaky-prone
|
|
40
|
+
/--------\
|
|
41
|
+
/ \ Integration Tests (20-30%)
|
|
42
|
+
/ Service \ - API contracts, database
|
|
43
|
+
/--------------\ - Component interactions
|
|
44
|
+
/ \
|
|
45
|
+
/ Unit Tests \ Unit Tests (60-70%)
|
|
46
|
+
/==================\ - Fast, isolated, deterministic
|
|
47
|
+
- Business logic focus
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
DistributionGuidelines {
|
|
51
|
+
| Test Type | Target % | Execution Time | Scope |
|
|
52
|
+
|-------------|----------|----------------|--------------------------|
|
|
53
|
+
| Unit | 60-70% | < 100ms each | Single function/class |
|
|
54
|
+
| Integration | 20-30% | < 5s each | Service boundaries |
|
|
55
|
+
| E2E | 5-10% | < 30s each | Critical user paths |
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
CorePatterns {
|
|
60
|
+
TestBehaviorNotImplementation {
|
|
61
|
+
Tests should verify observable behavior, not internal implementation details
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// CORRECT: Tests behavior
|
|
65
|
+
describe('ShoppingCart', () => {
|
|
66
|
+
it('calculates total with quantity discounts', () => {
|
|
67
|
+
const cart = new ShoppingCart();
|
|
68
|
+
cart.add({ sku: 'WIDGET', price: 10, quantity: 5 });
|
|
69
|
+
|
|
70
|
+
expect(cart.total).toBe(45); // 10% discount for 5+ items
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// INCORRECT: Tests implementation
|
|
75
|
+
describe('ShoppingCart', () => {
|
|
76
|
+
it('calls _applyDiscount method', () => {
|
|
77
|
+
const cart = new ShoppingCart();
|
|
78
|
+
const spy = jest.spyOn(cart, '_applyDiscount');
|
|
79
|
+
|
|
80
|
+
cart.add({ sku: 'WIDGET', price: 10, quantity: 5 });
|
|
81
|
+
|
|
82
|
+
expect(spy).toHaveBeenCalledWith(0.1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
ArrangeActAssertStructure {
|
|
89
|
+
Every test follows the AAA pattern for clarity and consistency
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
def test_user_registration_sends_welcome_email():
|
|
93
|
+
# Arrange
|
|
94
|
+
email_service = MockEmailService()
|
|
95
|
+
user_service = UserService(email_service=email_service)
|
|
96
|
+
registration_data = {"email": "new@user.com", "name": "New User"}
|
|
97
|
+
|
|
98
|
+
# Act
|
|
99
|
+
user_service.register(registration_data)
|
|
100
|
+
|
|
101
|
+
# Assert
|
|
102
|
+
assert email_service.sent_emails == [
|
|
103
|
+
{"to": "new@user.com", "template": "welcome"}
|
|
104
|
+
]
|
|
105
|
+
```
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
OneAssertionPerBehavior {
|
|
109
|
+
Each test verifies one specific behavior
|
|
110
|
+
Multiple assertions acceptable when verifying a single logical outcome
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// CORRECT: One behavior, multiple related assertions
|
|
114
|
+
it('creates order with correct initial state', () => {
|
|
115
|
+
const order = createOrder(items);
|
|
116
|
+
|
|
117
|
+
expect(order.status).toBe('pending');
|
|
118
|
+
expect(order.items).toHaveLength(items.length);
|
|
119
|
+
expect(order.createdAt).toBeInstanceOf(Date);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// INCORRECT: Multiple unrelated behaviors
|
|
123
|
+
it('creates and processes order', () => {
|
|
124
|
+
const order = createOrder(items);
|
|
125
|
+
expect(order.status).toBe('pending');
|
|
126
|
+
|
|
127
|
+
processPayment(order);
|
|
128
|
+
expect(order.status).toBe('paid'); // Different behavior
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
DescriptiveTestNames {
|
|
134
|
+
Test names describe the scenario and expected outcome
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Format: [unit]_[scenario]_[expected outcome]
|
|
138
|
+
// or: [action]_[condition]_[result]
|
|
139
|
+
|
|
140
|
+
// CORRECT
|
|
141
|
+
it('calculateTotal returns zero for empty cart')
|
|
142
|
+
it('validateEmail rejects addresses without @ symbol')
|
|
143
|
+
it('UserService throws NotFoundError when user does not exist')
|
|
144
|
+
|
|
145
|
+
// INCORRECT
|
|
146
|
+
it('test calculateTotal')
|
|
147
|
+
it('validateEmail works')
|
|
148
|
+
it('error handling')
|
|
149
|
+
```
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
TestIsolation {
|
|
153
|
+
Tests must be independent and not share mutable state
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
# CORRECT: Fresh fixture per test
|
|
157
|
+
class TestOrderService:
|
|
158
|
+
def setup_method(self):
|
|
159
|
+
self.db = InMemoryDatabase()
|
|
160
|
+
self.service = OrderService(self.db)
|
|
161
|
+
|
|
162
|
+
def test_create_order(self):
|
|
163
|
+
order = self.service.create(items=[...])
|
|
164
|
+
assert order.id is not None
|
|
165
|
+
|
|
166
|
+
def test_list_orders_empty(self):
|
|
167
|
+
orders = self.service.list()
|
|
168
|
+
assert orders == []
|
|
169
|
+
|
|
170
|
+
# INCORRECT: Shared state between tests
|
|
171
|
+
db = Database() # Shared!
|
|
172
|
+
|
|
173
|
+
def test_create_order():
|
|
174
|
+
order = OrderService(db).create(items=[...])
|
|
175
|
+
|
|
176
|
+
def test_list_orders_empty():
|
|
177
|
+
orders = OrderService(db).list() # May see order from previous test!
|
|
178
|
+
```
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
CoverageTargets {
|
|
183
|
+
RecommendedCoverageByCodeType {
|
|
184
|
+
| Code Type | Statement | Branch | Target |
|
|
185
|
+
|--------------------|-----------|--------|--------|
|
|
186
|
+
| Business Logic | 90% | 85% | High |
|
|
187
|
+
| API Controllers | 80% | 75% | Medium |
|
|
188
|
+
| Utility Functions | 95% | 90% | High |
|
|
189
|
+
| UI Components | 70% | 65% | Medium |
|
|
190
|
+
| Generated Code | N/A | N/A | Skip |
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
CoverageQualityOverQuantity {
|
|
194
|
+
Coverage percentage alone is insufficient. Prioritize:
|
|
195
|
+
|
|
196
|
+
1. Critical paths: Authentication, payments, data integrity
|
|
197
|
+
2. Edge cases: Boundaries, empty states, error conditions
|
|
198
|
+
3. Regression prevention: Previously broken functionality
|
|
199
|
+
4. Complex logic: High cyclomatic complexity areas
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
FrameworkSpecificPatterns {
|
|
204
|
+
Jest {
|
|
205
|
+
```typescript
|
|
206
|
+
// File structure
|
|
207
|
+
src/
|
|
208
|
+
services/
|
|
209
|
+
UserService.ts
|
|
210
|
+
UserService.test.ts // Co-located
|
|
211
|
+
|
|
212
|
+
// Test setup
|
|
213
|
+
describe('UserService', () => {
|
|
214
|
+
let userService: UserService;
|
|
215
|
+
let mockRepo: jest.Mocked<UserRepository>;
|
|
216
|
+
|
|
217
|
+
beforeEach(() => {
|
|
218
|
+
mockRepo = {
|
|
219
|
+
findById: jest.fn(),
|
|
220
|
+
save: jest.fn(),
|
|
221
|
+
};
|
|
222
|
+
userService = new UserService(mockRepo);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
afterEach(() => {
|
|
226
|
+
jest.clearAllMocks();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('getUser', () => {
|
|
230
|
+
it('returns user when found', async () => {
|
|
231
|
+
const expected = { id: '1', name: 'Alice' };
|
|
232
|
+
mockRepo.findById.mockResolvedValue(expected);
|
|
233
|
+
|
|
234
|
+
const result = await userService.getUser('1');
|
|
235
|
+
|
|
236
|
+
expect(result).toEqual(expected);
|
|
237
|
+
expect(mockRepo.findById).toHaveBeenCalledWith('1');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('throws NotFoundError when user does not exist', async () => {
|
|
241
|
+
mockRepo.findById.mockResolvedValue(null);
|
|
242
|
+
|
|
243
|
+
await expect(userService.getUser('999'))
|
|
244
|
+
.rejects.toThrow(NotFoundError);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Pytest {
|
|
252
|
+
```python
|
|
253
|
+
# File structure
|
|
254
|
+
src/
|
|
255
|
+
services/
|
|
256
|
+
user_service.py
|
|
257
|
+
tests/
|
|
258
|
+
services/
|
|
259
|
+
test_user_service.py # Mirror structure
|
|
260
|
+
|
|
261
|
+
# conftest.py - Shared fixtures
|
|
262
|
+
import pytest
|
|
263
|
+
from unittest.mock import Mock
|
|
264
|
+
|
|
265
|
+
@pytest.fixture
|
|
266
|
+
def mock_user_repo():
|
|
267
|
+
return Mock(spec=UserRepository)
|
|
268
|
+
|
|
269
|
+
@pytest.fixture
|
|
270
|
+
def user_service(mock_user_repo):
|
|
271
|
+
return UserService(repository=mock_user_repo)
|
|
272
|
+
|
|
273
|
+
# test_user_service.py
|
|
274
|
+
class TestUserService:
|
|
275
|
+
def test_get_user_returns_user_when_found(
|
|
276
|
+
self, user_service, mock_user_repo
|
|
277
|
+
):
|
|
278
|
+
expected = User(id="1", name="Alice")
|
|
279
|
+
mock_user_repo.find_by_id.return_value = expected
|
|
280
|
+
|
|
281
|
+
result = user_service.get_user("1")
|
|
282
|
+
|
|
283
|
+
assert result == expected
|
|
284
|
+
mock_user_repo.find_by_id.assert_called_once_with("1")
|
|
285
|
+
|
|
286
|
+
def test_get_user_raises_not_found_when_missing(
|
|
287
|
+
self, user_service, mock_user_repo
|
|
288
|
+
):
|
|
289
|
+
mock_user_repo.find_by_id.return_value = None
|
|
290
|
+
|
|
291
|
+
with pytest.raises(NotFoundError):
|
|
292
|
+
user_service.get_user("999")
|
|
293
|
+
|
|
294
|
+
@pytest.mark.parametrize("invalid_email", [
|
|
295
|
+
"",
|
|
296
|
+
"no-at-sign",
|
|
297
|
+
"@no-local-part.com",
|
|
298
|
+
"no-domain@",
|
|
299
|
+
])
|
|
300
|
+
def test_validate_email_rejects_invalid_formats(
|
|
301
|
+
self, user_service, invalid_email
|
|
302
|
+
):
|
|
303
|
+
assert not user_service.validate_email(invalid_email)
|
|
304
|
+
```
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
ReactTestingLibrary {
|
|
308
|
+
```typescript
|
|
309
|
+
// Component test
|
|
310
|
+
import { render, screen, userEvent } from '@testing-library/react';
|
|
311
|
+
import { LoginForm } from './LoginForm';
|
|
312
|
+
|
|
313
|
+
describe('LoginForm', () => {
|
|
314
|
+
it('calls onSubmit with credentials when form is submitted', async () => {
|
|
315
|
+
const onSubmit = jest.fn();
|
|
316
|
+
const user = userEvent.setup();
|
|
317
|
+
|
|
318
|
+
render(<LoginForm onSubmit={onSubmit} />);
|
|
319
|
+
|
|
320
|
+
await user.type(screen.getByLabelText('Email'), 'test@example.com');
|
|
321
|
+
await user.type(screen.getByLabelText('Password'), 'secret123');
|
|
322
|
+
await user.click(screen.getByRole('button', { name: 'Sign In' }));
|
|
323
|
+
|
|
324
|
+
expect(onSubmit).toHaveBeenCalledWith({
|
|
325
|
+
email: 'test@example.com',
|
|
326
|
+
password: 'secret123',
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('displays validation error for invalid email', async () => {
|
|
331
|
+
const user = userEvent.setup();
|
|
332
|
+
|
|
333
|
+
render(<LoginForm onSubmit={jest.fn()} />);
|
|
334
|
+
|
|
335
|
+
await user.type(screen.getByLabelText('Email'), 'invalid');
|
|
336
|
+
await user.click(screen.getByRole('button', { name: 'Sign In' }));
|
|
337
|
+
|
|
338
|
+
expect(screen.getByText('Please enter a valid email')).toBeInTheDocument();
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
TestOrganization {
|
|
346
|
+
FileNamingConventions {
|
|
347
|
+
| Framework | Test File Pattern | Example |
|
|
348
|
+
|-----------|------------------------|----------------------------|
|
|
349
|
+
| Jest | `*.test.ts` | `UserService.test.ts` |
|
|
350
|
+
| Pytest | `test_*.py` | `test_user_service.py` |
|
|
351
|
+
| Go | `*_test.go` | `user_service_test.go` |
|
|
352
|
+
| JUnit | `*Test.java` | `UserServiceTest.java` |
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
DirectoryStructurePatterns {
|
|
356
|
+
CoLocatedTests {
|
|
357
|
+
Preferred for unit tests:
|
|
358
|
+
```
|
|
359
|
+
src/
|
|
360
|
+
services/
|
|
361
|
+
UserService.ts
|
|
362
|
+
UserService.test.ts
|
|
363
|
+
utils/
|
|
364
|
+
validators.ts
|
|
365
|
+
validators.test.ts
|
|
366
|
+
```
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
SeparateTestDirectory {
|
|
370
|
+
For integration/E2E:
|
|
371
|
+
```
|
|
372
|
+
src/
|
|
373
|
+
services/
|
|
374
|
+
UserService.ts
|
|
375
|
+
tests/
|
|
376
|
+
unit/
|
|
377
|
+
services/
|
|
378
|
+
UserService.test.ts
|
|
379
|
+
integration/
|
|
380
|
+
api/
|
|
381
|
+
users.test.ts
|
|
382
|
+
e2e/
|
|
383
|
+
user-registration.spec.ts
|
|
384
|
+
```
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
BestPractices {
|
|
390
|
+
- Run tests before committing; never commit failing tests
|
|
391
|
+
- Keep unit tests under 100ms execution time
|
|
392
|
+
- Mock external dependencies at service boundaries only
|
|
393
|
+
- Use factories or fixtures for test data, not raw literals
|
|
394
|
+
- Delete flaky tests or fix them immediately
|
|
395
|
+
- Review tests during code review with same rigor as production code
|
|
396
|
+
- Name tests as specifications that document behavior
|
|
397
|
+
- Prefer real implementations over mocks when practical
|
|
398
|
+
- Test edge cases: nulls, empty collections, boundaries
|
|
399
|
+
- Avoid conditional logic in tests
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
AntiPatterns {
|
|
403
|
+
TestingImplementationDetails {
|
|
404
|
+
```typescript
|
|
405
|
+
// WRONG: Brittle test that breaks on refactoring
|
|
406
|
+
it('stores user in _users array', () => {
|
|
407
|
+
const service = new UserService();
|
|
408
|
+
service.addUser(user);
|
|
409
|
+
expect(service._users).toContain(user);
|
|
410
|
+
});
|
|
411
|
+
```
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
SharedMutableState {
|
|
415
|
+
```python
|
|
416
|
+
# WRONG: Tests interfere with each other
|
|
417
|
+
users = []
|
|
418
|
+
|
|
419
|
+
def test_add_user():
|
|
420
|
+
users.append(User())
|
|
421
|
+
|
|
422
|
+
def test_user_count():
|
|
423
|
+
assert len(users) == 0 # Fails if test_add_user runs first
|
|
424
|
+
```
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
OverMocking {
|
|
428
|
+
```typescript
|
|
429
|
+
// WRONG: Mocking the system under test
|
|
430
|
+
it('processes payment', () => {
|
|
431
|
+
const processor = new PaymentProcessor();
|
|
432
|
+
jest.spyOn(processor, 'validate').mockReturnValue(true);
|
|
433
|
+
jest.spyOn(processor, 'charge').mockResolvedValue({ success: true });
|
|
434
|
+
|
|
435
|
+
// What are we even testing?
|
|
436
|
+
const result = processor.process(payment);
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
TestDuplication {
|
|
442
|
+
```python
|
|
443
|
+
# WRONG: Copy-paste tests with minor variations
|
|
444
|
+
def test_validate_email_empty():
|
|
445
|
+
assert not validate_email("")
|
|
446
|
+
|
|
447
|
+
def test_validate_email_no_at():
|
|
448
|
+
assert not validate_email("invalid")
|
|
449
|
+
|
|
450
|
+
def test_validate_email_no_domain():
|
|
451
|
+
assert not validate_email("user@")
|
|
452
|
+
|
|
453
|
+
# CORRECT: Parameterized test
|
|
454
|
+
@pytest.mark.parametrize("invalid_email", ["", "invalid", "user@"])
|
|
455
|
+
def test_validate_email_rejects_invalid_formats(invalid_email):
|
|
456
|
+
assert not validate_email(invalid_email)
|
|
457
|
+
```
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
## References
|
|
463
|
+
|
|
464
|
+
- `examples/test-pyramid.md` - Detailed test pyramid implementation guide with framework examples
|