trinity-method-sdk 2.0.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 +116 -0
- package/LICENSE +21 -0
- package/README.md +555 -0
- package/dist/cli/commands/deploy/agents.d.ts +14 -0
- package/dist/cli/commands/deploy/agents.js +59 -0
- package/dist/cli/commands/deploy/ci-cd.d.ts +13 -0
- package/dist/cli/commands/deploy/ci-cd.js +50 -0
- package/dist/cli/commands/deploy/claude-setup.d.ts +17 -0
- package/dist/cli/commands/deploy/claude-setup.js +91 -0
- package/dist/cli/commands/deploy/configuration.d.ts +13 -0
- package/dist/cli/commands/deploy/configuration.js +215 -0
- package/dist/cli/commands/deploy/directories.d.ts +12 -0
- package/dist/cli/commands/deploy/directories.js +38 -0
- package/dist/cli/commands/deploy/gitignore.d.ts +12 -0
- package/dist/cli/commands/deploy/gitignore.js +53 -0
- package/dist/cli/commands/deploy/index.d.ts +38 -0
- package/dist/cli/commands/deploy/index.js +156 -0
- package/dist/cli/commands/deploy/knowledge-base.d.ts +16 -0
- package/dist/cli/commands/deploy/knowledge-base.js +75 -0
- package/dist/cli/commands/deploy/linting.d.ts +18 -0
- package/dist/cli/commands/deploy/linting.js +51 -0
- package/dist/cli/commands/deploy/metrics.d.ts +13 -0
- package/dist/cli/commands/deploy/metrics.js +34 -0
- package/dist/cli/commands/deploy/pre-flight.d.ts +13 -0
- package/dist/cli/commands/deploy/pre-flight.js +29 -0
- package/dist/cli/commands/deploy/root-files.d.ts +16 -0
- package/dist/cli/commands/deploy/root-files.js +178 -0
- package/dist/cli/commands/deploy/sdk-install.d.ts +12 -0
- package/dist/cli/commands/deploy/sdk-install.js +57 -0
- package/dist/cli/commands/deploy/summary.d.ts +14 -0
- package/dist/cli/commands/deploy/summary.js +130 -0
- package/dist/cli/commands/deploy/templates.d.ts +14 -0
- package/dist/cli/commands/deploy/templates.js +84 -0
- package/dist/cli/commands/deploy/types.d.ts +39 -0
- package/dist/cli/commands/deploy/types.js +5 -0
- package/dist/cli/commands/update/agents.d.ts +14 -0
- package/dist/cli/commands/update/agents.js +31 -0
- package/dist/cli/commands/update/backup.d.ts +31 -0
- package/dist/cli/commands/update/backup.js +97 -0
- package/dist/cli/commands/update/commands.d.ts +14 -0
- package/dist/cli/commands/update/commands.js +75 -0
- package/dist/cli/commands/update/index.d.ts +15 -0
- package/dist/cli/commands/update/index.js +118 -0
- package/dist/cli/commands/update/knowledge-base.d.ts +14 -0
- package/dist/cli/commands/update/knowledge-base.js +38 -0
- package/dist/cli/commands/update/pre-flight.d.ts +13 -0
- package/dist/cli/commands/update/pre-flight.js +37 -0
- package/dist/cli/commands/update/summary.d.ts +20 -0
- package/dist/cli/commands/update/summary.js +47 -0
- package/dist/cli/commands/update/templates.d.ts +14 -0
- package/dist/cli/commands/update/templates.js +25 -0
- package/dist/cli/commands/update/types.d.ts +13 -0
- package/dist/cli/commands/update/types.js +7 -0
- package/dist/cli/commands/update/utils.d.ts +11 -0
- package/dist/cli/commands/update/utils.js +19 -0
- package/dist/cli/commands/update/verification.d.ts +20 -0
- package/dist/cli/commands/update/verification.js +54 -0
- package/dist/cli/commands/update/version.d.ts +18 -0
- package/dist/cli/commands/update/version.js +36 -0
- package/dist/cli/commands/update.d.ts +7 -0
- package/dist/cli/commands/update.js +7 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +36 -0
- package/dist/cli/types.d.ts +77 -0
- package/dist/cli/types.js +5 -0
- package/dist/cli/utils/deploy-ci.d.ts +22 -0
- package/dist/cli/utils/deploy-ci.js +138 -0
- package/dist/cli/utils/deploy-linting.d.ts +3 -0
- package/dist/cli/utils/deploy-linting.js +136 -0
- package/dist/cli/utils/detect-stack.d.ts +3 -0
- package/dist/cli/utils/detect-stack.js +270 -0
- package/dist/cli/utils/error-classes.d.ts +63 -0
- package/dist/cli/utils/error-classes.js +84 -0
- package/dist/cli/utils/error-handler.d.ts +59 -0
- package/dist/cli/utils/error-handler.js +127 -0
- package/dist/cli/utils/errors.d.ts +52 -0
- package/dist/cli/utils/errors.js +102 -0
- package/dist/cli/utils/get-sdk-path.d.ts +18 -0
- package/dist/cli/utils/get-sdk-path.js +31 -0
- package/dist/cli/utils/inject-dependencies.d.ts +2 -0
- package/dist/cli/utils/inject-dependencies.js +55 -0
- package/dist/cli/utils/linting-tools.d.ts +8 -0
- package/dist/cli/utils/linting-tools.js +206 -0
- package/dist/cli/utils/metrics/code-quality.d.ts +32 -0
- package/dist/cli/utils/metrics/code-quality.js +122 -0
- package/dist/cli/utils/metrics/dependency-parser.d.ts +21 -0
- package/dist/cli/utils/metrics/dependency-parser.js +153 -0
- package/dist/cli/utils/metrics/file-complexity.d.ts +26 -0
- package/dist/cli/utils/metrics/file-complexity.js +77 -0
- package/dist/cli/utils/metrics/framework-detector.d.ts +17 -0
- package/dist/cli/utils/metrics/framework-detector.js +120 -0
- package/dist/cli/utils/metrics/git-metrics.d.ts +30 -0
- package/dist/cli/utils/metrics/git-metrics.js +83 -0
- package/dist/cli/utils/metrics/index.d.ts +28 -0
- package/dist/cli/utils/metrics/index.js +100 -0
- package/dist/cli/utils/template-processor.d.ts +10 -0
- package/dist/cli/utils/template-processor.js +188 -0
- package/dist/cli/utils/validate-path.d.ts +80 -0
- package/dist/cli/utils/validate-path.js +180 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +8 -0
- package/dist/templates/agents/aj-team/apo-documentation-specialist.md.template +572 -0
- package/dist/templates/agents/aj-team/bas-quality-gate.md.template +906 -0
- package/dist/templates/agents/aj-team/bon-dependency-manager.md.template +636 -0
- package/dist/templates/agents/aj-team/cap-configuration-specialist.md.template +670 -0
- package/dist/templates/agents/aj-team/dra-code-reviewer.md.template +768 -0
- package/dist/templates/agents/aj-team/kil-task-executor.md.template +764 -0
- package/dist/templates/agents/aj-team/uro-refactoring-specialist.md.template +759 -0
- package/dist/templates/agents/audit/juno-auditor.md.template +447 -0
- package/dist/templates/agents/deployment/ein-cicd.md.template +694 -0
- package/dist/templates/agents/deployment/ino-context.md.template +733 -0
- package/dist/templates/agents/deployment/tan-structure.md.template +661 -0
- package/dist/templates/agents/deployment/zen-knowledge.md.template +451 -0
- package/dist/templates/agents/leadership/aj-cc.md.template +462 -0
- package/dist/templates/agents/leadership/aj-maestro.md.template +943 -0
- package/dist/templates/agents/leadership/aly-cto.md.template +407 -0
- package/dist/templates/agents/planning/eus-decomposer.md.template +496 -0
- package/dist/templates/agents/planning/mon-requirements.md.template +323 -0
- package/dist/templates/agents/planning/ror-design.md.template +465 -0
- package/dist/templates/agents/planning/tra-planner.md.template +432 -0
- package/dist/templates/ci/cd.yml.template +175 -0
- package/dist/templates/ci/ci.yml.template +196 -0
- package/dist/templates/ci/generic-ci.yml +115 -0
- package/dist/templates/ci/github-actions.yml +86 -0
- package/dist/templates/ci/gitlab-ci.yml +103 -0
- package/dist/templates/claude/EMPLOYEE-DIRECTORY.md.template +545 -0
- package/dist/templates/documentation/ROOT-README.md.template +307 -0
- package/dist/templates/documentation/SUBDIRECTORY-README.md.template +261 -0
- package/dist/templates/investigations/bug.md.template +484 -0
- package/dist/templates/investigations/feature.md.template +564 -0
- package/dist/templates/investigations/performance.md.template +625 -0
- package/dist/templates/investigations/security.md.template +714 -0
- package/dist/templates/investigations/technical.md.template +433 -0
- package/dist/templates/knowledge-base/AI-DEVELOPMENT-GUIDE.md.template +957 -0
- package/dist/templates/knowledge-base/ARCHITECTURE.md.template +452 -0
- package/dist/templates/knowledge-base/CODING-PRINCIPLES.md.template +750 -0
- package/dist/templates/knowledge-base/DOCUMENTATION-CRITERIA.md.template +1118 -0
- package/dist/templates/knowledge-base/ISSUES.md.template +539 -0
- package/dist/templates/knowledge-base/TESTING-PRINCIPLES.md.template +894 -0
- package/dist/templates/knowledge-base/Technical-Debt.md.template +640 -0
- package/dist/templates/knowledge-base/To-do.md.template +407 -0
- package/dist/templates/knowledge-base/Trinity.md.template +464 -0
- package/dist/templates/linting/flutter/.pre-commit-config.yaml.template +27 -0
- package/dist/templates/linting/flutter/analysis_options.yaml.template +26 -0
- package/dist/templates/linting/nodejs/.eslintrc-commonjs.json.template +19 -0
- package/dist/templates/linting/nodejs/.eslintrc-esm.json.template +19 -0
- package/dist/templates/linting/nodejs/.eslintrc-typescript.json.template +22 -0
- package/dist/templates/linting/nodejs/.pre-commit-config.yaml.template +51 -0
- package/dist/templates/linting/nodejs/.prettierrc.json.template +10 -0
- package/dist/templates/linting/python/.flake8.template +16 -0
- package/dist/templates/linting/python/.pre-commit-config.yaml.template +30 -0
- package/dist/templates/linting/python/pyproject.toml.template +38 -0
- package/dist/templates/linting/rust/.pre-commit-config.yaml.template +28 -0
- package/dist/templates/linting/rust/clippy.toml.template +14 -0
- package/dist/templates/linting/rust/rustfmt.toml.template +12 -0
- package/dist/templates/root/CLAUDE.md.template +65 -0
- package/dist/templates/root/TRINITY.md.template +52 -0
- package/dist/templates/shared/claude-commands/trinity-agents.md.template +168 -0
- package/dist/templates/shared/claude-commands/trinity-audit.md.template +646 -0
- package/dist/templates/shared/claude-commands/trinity-changelog.md.template +624 -0
- package/dist/templates/shared/claude-commands/trinity-continue.md.template +549 -0
- package/dist/templates/shared/claude-commands/trinity-create-investigation.md.template +232 -0
- package/dist/templates/shared/claude-commands/trinity-decompose.md.template +181 -0
- package/dist/templates/shared/claude-commands/trinity-design.md.template +347 -0
- package/dist/templates/shared/claude-commands/trinity-docs.md.template +2093 -0
- package/dist/templates/shared/claude-commands/trinity-end.md.template +397 -0
- package/dist/templates/shared/claude-commands/trinity-init.md.template +606 -0
- package/dist/templates/shared/claude-commands/trinity-investigate-templates.md.template +725 -0
- package/dist/templates/shared/claude-commands/trinity-orchestrate.md.template +1061 -0
- package/dist/templates/shared/claude-commands/trinity-plan-investigation.md.template +135 -0
- package/dist/templates/shared/claude-commands/trinity-plan.md.template +201 -0
- package/dist/templates/shared/claude-commands/trinity-readme.md.template +1971 -0
- package/dist/templates/shared/claude-commands/trinity-requirements.md.template +148 -0
- package/dist/templates/shared/claude-commands/trinity-start.md.template +268 -0
- package/dist/templates/shared/claude-commands/trinity-verify.md.template +453 -0
- package/dist/templates/shared/claude-commands/trinity-workorder.md.template +249 -0
- package/dist/templates/source/base-CLAUDE.md.template +310 -0
- package/dist/templates/source/flutter-CLAUDE.md.template +593 -0
- package/dist/templates/source/nodejs-CLAUDE.md.template +531 -0
- package/dist/templates/source/python-CLAUDE.md.template +510 -0
- package/dist/templates/source/react-CLAUDE.md.template +513 -0
- package/dist/templates/source/rust-CLAUDE.md.template +653 -0
- package/dist/templates/source/tests-CLAUDE.md.template +432 -0
- package/dist/templates/trinity/CLAUDE.md.template +372 -0
- package/dist/templates/work-orders/ANALYSIS-TEMPLATE.md.template +276 -0
- package/dist/templates/work-orders/AUDIT-TEMPLATE.md.template +262 -0
- package/dist/templates/work-orders/IMPLEMENTATION-TEMPLATE.md.template +260 -0
- package/dist/templates/work-orders/INVESTIGATION-TEMPLATE.md.template +206 -0
- package/dist/templates/work-orders/PATTERN-TEMPLATE.md.template +320 -0
- package/dist/templates/work-orders/VERIFICATION-TEMPLATE.md.template +273 -0
- package/package.json +94 -0
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
# Testing Principles
|
|
2
|
+
|
|
3
|
+
**Version**: 2.0.0
|
|
4
|
+
**Last Updated**: {{date}}
|
|
5
|
+
**Project**: {{projectName}}
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
This document establishes testing standards and best practices for {{projectName}}. All code must meet these testing requirements to ensure reliability, maintainability, and confidence in changes.
|
|
12
|
+
|
|
13
|
+
## 1. Test-Driven Development (TDD)
|
|
14
|
+
|
|
15
|
+
### 1.1 RED-GREEN-REFACTOR Cycle
|
|
16
|
+
|
|
17
|
+
**Rule**: All new features must follow the TDD cycle.
|
|
18
|
+
|
|
19
|
+
**The TDD Cycle**:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
🔴 RED: Write a failing test first
|
|
23
|
+
↓
|
|
24
|
+
🟢 GREEN: Write minimal code to make it pass
|
|
25
|
+
↓
|
|
26
|
+
🔵 REFACTOR: Improve code while keeping tests green
|
|
27
|
+
↓
|
|
28
|
+
[Repeat]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**✅ Good Example**:
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
// STEP 1: RED - Write failing test
|
|
35
|
+
describe('calculateTax', () => {
|
|
36
|
+
test('should calculate 10% tax on $100', () => {
|
|
37
|
+
expect(calculateTax(100, 0.10)).toBe(10);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Run test → FAILS (calculateTax doesn't exist yet)
|
|
42
|
+
|
|
43
|
+
// STEP 2: GREEN - Minimal implementation
|
|
44
|
+
function calculateTax(amount, rate) {
|
|
45
|
+
return amount * rate;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Run test → PASSES
|
|
49
|
+
|
|
50
|
+
// STEP 3: REFACTOR - Improve (add validation)
|
|
51
|
+
function calculateTax(amount, rate) {
|
|
52
|
+
if (typeof amount !== 'number' || amount < 0) {
|
|
53
|
+
throw new Error('Amount must be a positive number');
|
|
54
|
+
}
|
|
55
|
+
if (typeof rate !== 'number' || rate < 0 || rate > 1) {
|
|
56
|
+
throw new Error('Rate must be between 0 and 1');
|
|
57
|
+
}
|
|
58
|
+
return amount * rate;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Add test for validation
|
|
62
|
+
test('should throw error for negative amount', () => {
|
|
63
|
+
expect(() => calculateTax(-100, 0.10)).toThrow('Amount must be');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Run tests → STILL PASSES
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**❌ Bad Example (No TDD)**:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
// Write implementation first
|
|
73
|
+
function calculateTax(amount, rate) {
|
|
74
|
+
// 50 lines of complex logic
|
|
75
|
+
// No tests, no confidence
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Write tests later (or never)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 1.2 When to Use TDD
|
|
82
|
+
|
|
83
|
+
**Always TDD**:
|
|
84
|
+
- ✅ New features
|
|
85
|
+
- ✅ Bug fixes (write test that reproduces bug, then fix)
|
|
86
|
+
- ✅ Refactoring (tests ensure behavior unchanged)
|
|
87
|
+
|
|
88
|
+
**TDD Optional** (but still test):
|
|
89
|
+
- ⚠️ Exploratory prototypes (test after proving concept)
|
|
90
|
+
- ⚠️ UI components (harder to TDD, but test after)
|
|
91
|
+
- ⚠️ Glue code (thin integration layers)
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 2. Test Coverage
|
|
96
|
+
|
|
97
|
+
### 2.1 Coverage Requirements
|
|
98
|
+
|
|
99
|
+
**Rule**: Minimum 80% code coverage required.
|
|
100
|
+
|
|
101
|
+
**Coverage Metrics**:
|
|
102
|
+
- **Line Coverage**: ≥80% of lines executed
|
|
103
|
+
- **Branch Coverage**: ≥80% of branches (if/else) tested
|
|
104
|
+
- **Function Coverage**: ≥80% of functions called
|
|
105
|
+
- **Statement Coverage**: ≥80% of statements executed
|
|
106
|
+
|
|
107
|
+
**✅ Good Coverage Example**:
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// Function with good coverage
|
|
111
|
+
function validateUser(user) {
|
|
112
|
+
if (!user) {
|
|
113
|
+
throw new Error('User required');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!user.email) {
|
|
117
|
+
throw new Error('Email required');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!isValidEmail(user.email)) {
|
|
121
|
+
throw new Error('Invalid email');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Tests covering all branches
|
|
128
|
+
describe('validateUser', () => {
|
|
129
|
+
test('throws error when user is null', () => {
|
|
130
|
+
expect(() => validateUser(null)).toThrow('User required');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('throws error when email is missing', () => {
|
|
134
|
+
expect(() => validateUser({})).toThrow('Email required');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('throws error when email is invalid', () => {
|
|
138
|
+
expect(() => validateUser({ email: 'invalid' })).toThrow('Invalid email');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('returns true for valid user', () => {
|
|
142
|
+
expect(validateUser({ email: 'test@example.com' })).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Coverage: 100% lines, 100% branches ✅
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**❌ Poor Coverage Example**:
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
// Same function, inadequate tests
|
|
153
|
+
describe('validateUser', () => {
|
|
154
|
+
test('works with valid user', () => {
|
|
155
|
+
expect(validateUser({ email: 'test@example.com' })).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Coverage: 40% lines, 25% branches ❌
|
|
160
|
+
// Error paths not tested!
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 2.2 Coverage Exceptions
|
|
164
|
+
|
|
165
|
+
**When <80% is Acceptable**:
|
|
166
|
+
- Configuration files
|
|
167
|
+
- Type definitions
|
|
168
|
+
- Simple getters/setters
|
|
169
|
+
- Deprecated code marked for removal
|
|
170
|
+
|
|
171
|
+
**Mark exceptions explicitly**:
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
/* istanbul ignore next */
|
|
175
|
+
function legacyFunction() {
|
|
176
|
+
// Deprecated, will be removed in v3.0
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 2.3 Coverage Enforcement
|
|
181
|
+
|
|
182
|
+
**BAS Quality Gate Phase 5** enforces 80% coverage:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# Coverage check
|
|
186
|
+
npm run test:coverage
|
|
187
|
+
|
|
188
|
+
# Must meet threshold to pass quality gate
|
|
189
|
+
{
|
|
190
|
+
"jest": {
|
|
191
|
+
"coverageThreshold": {
|
|
192
|
+
"global": {
|
|
193
|
+
"lines": 80,
|
|
194
|
+
"branches": 80,
|
|
195
|
+
"functions": 80,
|
|
196
|
+
"statements": 80
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 3. Test Structure
|
|
206
|
+
|
|
207
|
+
### 3.1 AAA Pattern (Arrange-Act-Assert)
|
|
208
|
+
|
|
209
|
+
**Rule**: Structure tests using the AAA pattern for clarity.
|
|
210
|
+
|
|
211
|
+
**The Pattern**:
|
|
212
|
+
1. **Arrange**: Set up test data and preconditions
|
|
213
|
+
2. **Act**: Execute the code under test
|
|
214
|
+
3. **Assert**: Verify the expected outcome
|
|
215
|
+
|
|
216
|
+
**✅ Good Example**:
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
test('should calculate order total correctly', () => {
|
|
220
|
+
// ARRANGE: Set up test data
|
|
221
|
+
const order = {
|
|
222
|
+
items: [
|
|
223
|
+
{ price: 10, quantity: 2 }, // $20
|
|
224
|
+
{ price: 15, quantity: 1 } // $15
|
|
225
|
+
],
|
|
226
|
+
taxRate: 0.10,
|
|
227
|
+
shippingCost: 5
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// ACT: Execute function
|
|
231
|
+
const total = calculateOrderTotal(order);
|
|
232
|
+
|
|
233
|
+
// ASSERT: Verify result
|
|
234
|
+
expect(total).toBe(42.5); // (20 + 15) * 1.10 + 5 = 42.5
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**❌ Bad Example**:
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
test('calculates total', () => {
|
|
242
|
+
// Everything mixed together - hard to understand
|
|
243
|
+
expect(calculateOrderTotal({
|
|
244
|
+
items: [{ price: 10, quantity: 2 }, { price: 15, quantity: 1 }],
|
|
245
|
+
taxRate: 0.10,
|
|
246
|
+
shippingCost: 5
|
|
247
|
+
})).toBe(42.5);
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 3.2 One Assertion Per Test (Guideline)
|
|
252
|
+
|
|
253
|
+
**Rule**: Prefer one logical assertion per test (but multiple expects are OK if testing same concept).
|
|
254
|
+
|
|
255
|
+
**✅ Good Examples**:
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
// Single assertion - clear
|
|
259
|
+
test('should return user with correct ID', () => {
|
|
260
|
+
const user = findUserById('123');
|
|
261
|
+
expect(user.id).toBe('123');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('should return user with correct name', () => {
|
|
265
|
+
const user = findUserById('123');
|
|
266
|
+
expect(user.name).toBe('John Doe');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Multiple asserts OK when testing same concept
|
|
270
|
+
test('should return complete user object', () => {
|
|
271
|
+
const user = findUserById('123');
|
|
272
|
+
expect(user).toEqual({
|
|
273
|
+
id: '123',
|
|
274
|
+
name: 'John Doe',
|
|
275
|
+
email: 'john@example.com'
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**⚠️ Acceptable (Multiple related assertions)**:
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
test('should validate user object structure', () => {
|
|
284
|
+
const user = createUser('John', 'john@example.com');
|
|
285
|
+
expect(user).toHaveProperty('id');
|
|
286
|
+
expect(user).toHaveProperty('name');
|
|
287
|
+
expect(user).toHaveProperty('email');
|
|
288
|
+
expect(user).toHaveProperty('createdAt');
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**❌ Bad Example (Unrelated assertions)**:
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
test('user functions', () => {
|
|
296
|
+
expect(createUser('John')).toBeDefined(); // Test 1
|
|
297
|
+
expect(deleteUser('123')).toBe(true); // Test 2 - unrelated!
|
|
298
|
+
expect(findUsers()).toHaveLength(5); // Test 3 - unrelated!
|
|
299
|
+
});
|
|
300
|
+
// Split into 3 separate tests
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### 3.3 Descriptive Test Names
|
|
304
|
+
|
|
305
|
+
**Rule**: Test names should describe what they test and expected outcome.
|
|
306
|
+
|
|
307
|
+
**✅ Good Names**:
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
describe('UserService', () => {
|
|
311
|
+
describe('createUser', () => {
|
|
312
|
+
test('should create user with valid data', () => { /* ... */ });
|
|
313
|
+
|
|
314
|
+
test('should throw error when email is missing', () => { /* ... */ });
|
|
315
|
+
|
|
316
|
+
test('should throw error when email format is invalid', () => { /* ... */ });
|
|
317
|
+
|
|
318
|
+
test('should hash password before saving', () => { /* ... */ });
|
|
319
|
+
|
|
320
|
+
test('should set createdAt timestamp', () => { /* ... */ });
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**❌ Bad Names**:
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
describe('UserService', () => {
|
|
329
|
+
test('test1', () => { /* ... */ }); // What does this test?
|
|
330
|
+
test('works', () => { /* ... */ }); // What works?
|
|
331
|
+
test('user', () => { /* ... */ }); // Tests what about user?
|
|
332
|
+
test('should pass', () => { /* ... */ }); // Useless
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 4. Mocking & Stubbing
|
|
339
|
+
|
|
340
|
+
### 4.1 When to Mock
|
|
341
|
+
|
|
342
|
+
**Mock External Dependencies**:
|
|
343
|
+
- ✅ Database calls
|
|
344
|
+
- ✅ API requests
|
|
345
|
+
- ✅ File system operations
|
|
346
|
+
- ✅ Third-party services
|
|
347
|
+
- ✅ Time-dependent operations (Date.now, setTimeout)
|
|
348
|
+
|
|
349
|
+
**Don't Mock**:
|
|
350
|
+
- ❌ Internal business logic (test the real thing)
|
|
351
|
+
- ❌ Simple utilities (they're fast, test real)
|
|
352
|
+
- ❌ Everything (over-mocking = brittle tests)
|
|
353
|
+
|
|
354
|
+
**✅ Good Mocking Example**:
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
// Mock external API
|
|
358
|
+
jest.mock('../services/api');
|
|
359
|
+
const api = require('../services/api');
|
|
360
|
+
|
|
361
|
+
test('should fetch and return user data', async () => {
|
|
362
|
+
// ARRANGE: Mock API response
|
|
363
|
+
api.get.mockResolvedValue({
|
|
364
|
+
data: { id: '123', name: 'John' }
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
// ACT: Call function that uses API
|
|
368
|
+
const user = await getUserData('123');
|
|
369
|
+
|
|
370
|
+
// ASSERT: Verify behavior
|
|
371
|
+
expect(api.get).toHaveBeenCalledWith('/users/123');
|
|
372
|
+
expect(user).toEqual({ id: '123', name: 'John' });
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### 4.2 Mock Implementation Strategies
|
|
377
|
+
|
|
378
|
+
**Strategy 1: Jest Mock Functions**:
|
|
379
|
+
|
|
380
|
+
```javascript
|
|
381
|
+
const mockFn = jest.fn();
|
|
382
|
+
mockFn.mockReturnValue(42);
|
|
383
|
+
mockFn.mockResolvedValue('async result');
|
|
384
|
+
mockFn.mockRejectedValue(new Error('failure'));
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Strategy 2: Manual Mocks**:
|
|
388
|
+
|
|
389
|
+
```javascript
|
|
390
|
+
// __mocks__/database.js
|
|
391
|
+
module.exports = {
|
|
392
|
+
users: {
|
|
393
|
+
findOne: jest.fn(),
|
|
394
|
+
insert: jest.fn(),
|
|
395
|
+
delete: jest.fn()
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Strategy 3: Dependency Injection** (Recommended):
|
|
401
|
+
|
|
402
|
+
```javascript
|
|
403
|
+
// Production code
|
|
404
|
+
function createUserService(database) {
|
|
405
|
+
return {
|
|
406
|
+
async getUser(id) {
|
|
407
|
+
return await database.users.findOne({ id });
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Test code
|
|
413
|
+
test('should get user from database', async () => {
|
|
414
|
+
const mockDb = {
|
|
415
|
+
users: {
|
|
416
|
+
findOne: jest.fn().mockResolvedValue({ id: '123', name: 'John' })
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const service = createUserService(mockDb);
|
|
421
|
+
const user = await service.getUser('123');
|
|
422
|
+
|
|
423
|
+
expect(user).toEqual({ id: '123', name: 'John' });
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### 4.3 Verify Mock Interactions
|
|
428
|
+
|
|
429
|
+
**✅ Good Verification**:
|
|
430
|
+
|
|
431
|
+
```javascript
|
|
432
|
+
test('should call emailService.send with correct params', async () => {
|
|
433
|
+
const mockEmailService = { send: jest.fn() };
|
|
434
|
+
|
|
435
|
+
await sendWelcomeEmail('user@example.com', mockEmailService);
|
|
436
|
+
|
|
437
|
+
expect(mockEmailService.send).toHaveBeenCalledWith({
|
|
438
|
+
to: 'user@example.com',
|
|
439
|
+
subject: 'Welcome!',
|
|
440
|
+
body: expect.stringContaining('Welcome to our platform')
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
expect(mockEmailService.send).toHaveBeenCalledTimes(1);
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## 5. Test Types
|
|
450
|
+
|
|
451
|
+
### 5.1 Unit Tests (60% of test suite)
|
|
452
|
+
|
|
453
|
+
**Purpose**: Test individual functions/modules in isolation.
|
|
454
|
+
|
|
455
|
+
**Characteristics**:
|
|
456
|
+
- Fast (milliseconds)
|
|
457
|
+
- Isolated (no external dependencies)
|
|
458
|
+
- Focused (one function/module)
|
|
459
|
+
|
|
460
|
+
**✅ Good Unit Test**:
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
describe('formatCurrency', () => {
|
|
464
|
+
test('should format USD correctly', () => {
|
|
465
|
+
expect(formatCurrency(1234.56, 'USD')).toBe('$1,234.56');
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test('should format EUR correctly', () => {
|
|
469
|
+
expect(formatCurrency(1234.56, 'EUR')).toBe('€1,234.56');
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test('should round to 2 decimal places', () => {
|
|
473
|
+
expect(formatCurrency(1234.567, 'USD')).toBe('$1,234.57');
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### 5.2 Integration Tests (30% of test suite)
|
|
479
|
+
|
|
480
|
+
**Purpose**: Test how multiple modules work together.
|
|
481
|
+
|
|
482
|
+
**Characteristics**:
|
|
483
|
+
- Slower (seconds)
|
|
484
|
+
- Tests interactions between modules
|
|
485
|
+
- May use real database (test DB) or API
|
|
486
|
+
|
|
487
|
+
**✅ Good Integration Test**:
|
|
488
|
+
|
|
489
|
+
```javascript
|
|
490
|
+
describe('User Registration Flow', () => {
|
|
491
|
+
test('should create user and send welcome email', async () => {
|
|
492
|
+
// Uses real userService + emailService (mocked SMTP)
|
|
493
|
+
const result = await registerUser({
|
|
494
|
+
email: 'test@example.com',
|
|
495
|
+
password: 'secure123'
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// Verify user created
|
|
499
|
+
const user = await db.users.findOne({ email: 'test@example.com' });
|
|
500
|
+
expect(user).toBeDefined();
|
|
501
|
+
expect(user.email).toBe('test@example.com');
|
|
502
|
+
|
|
503
|
+
// Verify email sent
|
|
504
|
+
expect(emailService.lastSentEmail).toMatchObject({
|
|
505
|
+
to: 'test@example.com',
|
|
506
|
+
subject: 'Welcome!'
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### 5.3 End-to-End Tests (10% of test suite)
|
|
513
|
+
|
|
514
|
+
**Purpose**: Test complete user flows through the entire system.
|
|
515
|
+
|
|
516
|
+
**Characteristics**:
|
|
517
|
+
- Slowest (minutes)
|
|
518
|
+
- Tests real scenarios
|
|
519
|
+
- May use real browser (Puppeteer, Playwright)
|
|
520
|
+
|
|
521
|
+
**✅ Good E2E Test**:
|
|
522
|
+
|
|
523
|
+
```javascript
|
|
524
|
+
describe('E2E: User Login', () => {
|
|
525
|
+
test('user can log in and see dashboard', async () => {
|
|
526
|
+
// Open browser
|
|
527
|
+
await page.goto('http://localhost:3000');
|
|
528
|
+
|
|
529
|
+
// Fill login form
|
|
530
|
+
await page.fill('input[name="email"]', 'test@example.com');
|
|
531
|
+
await page.fill('input[name="password"]', 'password123');
|
|
532
|
+
await page.click('button[type="submit"]');
|
|
533
|
+
|
|
534
|
+
// Verify redirected to dashboard
|
|
535
|
+
await page.waitForURL('http://localhost:3000/dashboard');
|
|
536
|
+
|
|
537
|
+
// Verify user name displayed
|
|
538
|
+
const userName = await page.textContent('.user-name');
|
|
539
|
+
expect(userName).toBe('Test User');
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### 5.4 Test Pyramid
|
|
545
|
+
|
|
546
|
+
```
|
|
547
|
+
/\
|
|
548
|
+
/E2E\ 10% - End-to-End (slow, comprehensive)
|
|
549
|
+
/______\
|
|
550
|
+
/ \
|
|
551
|
+
/Integration\ 30% - Integration (medium speed)
|
|
552
|
+
/____________\
|
|
553
|
+
/ \
|
|
554
|
+
/ Unit Tests \ 60% - Unit (fast, focused)
|
|
555
|
+
/__________________\
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Balance** (Total: 100%):
|
|
559
|
+
- **60% Unit Tests**: Fast feedback, test individual functions
|
|
560
|
+
- **30% Integration Tests**: Test module interactions
|
|
561
|
+
- **10% E2E Tests**: Test critical user flows
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## 6. Test Organization
|
|
566
|
+
|
|
567
|
+
### 6.1 File Structure
|
|
568
|
+
|
|
569
|
+
**Rule**: Mirror source directory structure in test directory.
|
|
570
|
+
|
|
571
|
+
**✅ Good Structure**:
|
|
572
|
+
|
|
573
|
+
```
|
|
574
|
+
src/
|
|
575
|
+
services/
|
|
576
|
+
userService.js
|
|
577
|
+
emailService.js
|
|
578
|
+
utils/
|
|
579
|
+
validation.js
|
|
580
|
+
|
|
581
|
+
tests/
|
|
582
|
+
services/
|
|
583
|
+
userService.test.js
|
|
584
|
+
emailService.test.js
|
|
585
|
+
utils/
|
|
586
|
+
validation.test.js
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Alternative** (Co-located):
|
|
590
|
+
|
|
591
|
+
```
|
|
592
|
+
src/
|
|
593
|
+
services/
|
|
594
|
+
userService.js
|
|
595
|
+
userService.test.js
|
|
596
|
+
emailService.js
|
|
597
|
+
emailService.test.js
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### 6.2 Test Naming Convention
|
|
601
|
+
|
|
602
|
+
**Rule**: Test files end with `.test.js` or `.spec.js`.
|
|
603
|
+
|
|
604
|
+
```
|
|
605
|
+
✅ userService.test.js
|
|
606
|
+
✅ userService.spec.js
|
|
607
|
+
❌ userService-test.js
|
|
608
|
+
❌ test_userService.js
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### 6.3 Setup and Teardown
|
|
612
|
+
|
|
613
|
+
**Use beforeEach/afterEach for common setup**:
|
|
614
|
+
|
|
615
|
+
```javascript
|
|
616
|
+
describe('UserService', () => {
|
|
617
|
+
let db;
|
|
618
|
+
let userService;
|
|
619
|
+
|
|
620
|
+
beforeEach(() => {
|
|
621
|
+
// Setup: Create fresh test database
|
|
622
|
+
db = createTestDatabase();
|
|
623
|
+
userService = createUserService(db);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
afterEach(() => {
|
|
627
|
+
// Teardown: Clean up
|
|
628
|
+
db.close();
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test('should create user', async () => {
|
|
632
|
+
const user = await userService.createUser({ email: 'test@example.com' });
|
|
633
|
+
expect(user).toBeDefined();
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test('should delete user', async () => {
|
|
637
|
+
const user = await userService.createUser({ email: 'test@example.com' });
|
|
638
|
+
const result = await userService.deleteUser(user.id);
|
|
639
|
+
expect(result).toBe(true);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## 7. Test Quality
|
|
647
|
+
|
|
648
|
+
### 7.1 Avoid Flaky Tests
|
|
649
|
+
|
|
650
|
+
**Flaky Test**: Test that sometimes passes, sometimes fails (unreliable).
|
|
651
|
+
|
|
652
|
+
**Common Causes**:
|
|
653
|
+
- ❌ Timing issues (async operations, timeouts)
|
|
654
|
+
- ❌ Shared state between tests
|
|
655
|
+
- ❌ Dependency on external services
|
|
656
|
+
- ❌ Test order dependencies
|
|
657
|
+
|
|
658
|
+
**✅ Fixes**:
|
|
659
|
+
|
|
660
|
+
```javascript
|
|
661
|
+
// BAD: Flaky due to timing
|
|
662
|
+
test('should update after 100ms', async () => {
|
|
663
|
+
updateAfterDelay();
|
|
664
|
+
await sleep(100);
|
|
665
|
+
expect(value).toBe('updated'); // Might fail if delay varies
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
// GOOD: Wait for actual condition
|
|
669
|
+
test('should update after delay', async () => {
|
|
670
|
+
updateAfterDelay();
|
|
671
|
+
await waitFor(() => expect(value).toBe('updated'), { timeout: 1000 });
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// BAD: Shared state (tests affect each other)
|
|
675
|
+
let counter = 0;
|
|
676
|
+
test('increments counter', () => {
|
|
677
|
+
counter++;
|
|
678
|
+
expect(counter).toBe(1); // Fails if run after other tests
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// GOOD: Isolated state
|
|
682
|
+
test('increments counter', () => {
|
|
683
|
+
let counter = 0;
|
|
684
|
+
counter++;
|
|
685
|
+
expect(counter).toBe(1);
|
|
686
|
+
});
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### 7.2 Test Independence
|
|
690
|
+
|
|
691
|
+
**Rule**: Tests must not depend on each other. Each test should pass in isolation.
|
|
692
|
+
|
|
693
|
+
```javascript
|
|
694
|
+
// ❌ BAD: Tests depend on order
|
|
695
|
+
let user;
|
|
696
|
+
|
|
697
|
+
test('creates user', () => {
|
|
698
|
+
user = createUser('John');
|
|
699
|
+
expect(user).toBeDefined();
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
test('deletes user', () => {
|
|
703
|
+
deleteUser(user.id); // Fails if run alone!
|
|
704
|
+
expect(findUser(user.id)).toBeUndefined();
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// ✅ GOOD: Each test independent
|
|
708
|
+
test('creates user', () => {
|
|
709
|
+
const user = createUser('John');
|
|
710
|
+
expect(user).toBeDefined();
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
test('deletes user', () => {
|
|
714
|
+
const user = createUser('John'); // Create own test data
|
|
715
|
+
deleteUser(user.id);
|
|
716
|
+
expect(findUser(user.id)).toBeUndefined();
|
|
717
|
+
});
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### 7.3 Avoid Test Logic
|
|
721
|
+
|
|
722
|
+
**Rule**: Tests should not contain complex logic (loops, conditionals).
|
|
723
|
+
|
|
724
|
+
```javascript
|
|
725
|
+
// ❌ BAD: Logic in test (hard to debug)
|
|
726
|
+
test('validates all users', () => {
|
|
727
|
+
const users = getUsers();
|
|
728
|
+
for (const user of users) {
|
|
729
|
+
if (user.email) {
|
|
730
|
+
expect(isValidEmail(user.email)).toBe(true);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
// ✅ GOOD: Explicit tests
|
|
736
|
+
test('validates user with email', () => {
|
|
737
|
+
const user = { email: 'test@example.com' };
|
|
738
|
+
expect(isValidEmail(user.email)).toBe(true);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
test('handles user without email', () => {
|
|
742
|
+
const user = {};
|
|
743
|
+
expect(user.email).toBeUndefined();
|
|
744
|
+
});
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## 8. Testing Best Practices
|
|
750
|
+
|
|
751
|
+
### 8.1 Fast Tests
|
|
752
|
+
|
|
753
|
+
**Rule**: Unit tests should run in <1 second each.
|
|
754
|
+
|
|
755
|
+
**Tips**:
|
|
756
|
+
- Mock slow operations (DB, API, file I/O)
|
|
757
|
+
- Use in-memory databases for integration tests
|
|
758
|
+
- Parallelize test execution
|
|
759
|
+
- Limit E2E tests to critical paths
|
|
760
|
+
|
|
761
|
+
### 8.2 Readable Tests
|
|
762
|
+
|
|
763
|
+
**Rule**: Tests are documentation. Write them for humans.
|
|
764
|
+
|
|
765
|
+
```javascript
|
|
766
|
+
// ✅ GOOD: Readable
|
|
767
|
+
test('should reject password shorter than 8 characters', () => {
|
|
768
|
+
expect(() => validatePassword('short')).toThrow('at least 8 characters');
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
// ❌ BAD: Cryptic
|
|
772
|
+
test('pwd val', () => {
|
|
773
|
+
expect(() => v('x')).toThrow();
|
|
774
|
+
});
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### 8.3 Don't Test Implementation Details
|
|
778
|
+
|
|
779
|
+
**Rule**: Test behavior, not implementation.
|
|
780
|
+
|
|
781
|
+
```javascript
|
|
782
|
+
// ❌ BAD: Testing implementation (brittle)
|
|
783
|
+
test('should call _internalHelper', () => {
|
|
784
|
+
const spy = jest.spyOn(service, '_internalHelper');
|
|
785
|
+
service.publicMethod();
|
|
786
|
+
expect(spy).toHaveBeenCalled();
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
// ✅ GOOD: Testing behavior (stable)
|
|
790
|
+
test('should return formatted user data', () => {
|
|
791
|
+
const result = service.publicMethod();
|
|
792
|
+
expect(result).toEqual({ id: '123', name: 'John Doe' });
|
|
793
|
+
});
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
---
|
|
797
|
+
|
|
798
|
+
## 9. Continuous Testing
|
|
799
|
+
|
|
800
|
+
### 9.1 Watch Mode
|
|
801
|
+
|
|
802
|
+
**Rule**: Run tests automatically on file changes during development.
|
|
803
|
+
|
|
804
|
+
```bash
|
|
805
|
+
npm run test:watch
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### 9.2 Pre-Commit Hook
|
|
809
|
+
|
|
810
|
+
**Rule**: All tests must pass before committing.
|
|
811
|
+
|
|
812
|
+
```bash
|
|
813
|
+
# .husky/pre-commit
|
|
814
|
+
npm test || exit 1
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
### 9.3 CI/CD Integration
|
|
818
|
+
|
|
819
|
+
**Rule**: All tests must pass in CI before merging.
|
|
820
|
+
|
|
821
|
+
```yaml
|
|
822
|
+
# .github/workflows/test.yml
|
|
823
|
+
- name: Run Tests
|
|
824
|
+
run: npm test
|
|
825
|
+
|
|
826
|
+
- name: Check Coverage
|
|
827
|
+
run: npm run test:coverage
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
---
|
|
831
|
+
|
|
832
|
+
## 10. Enforcement
|
|
833
|
+
|
|
834
|
+
### BAS Quality Gate (Phase 4 & 5)
|
|
835
|
+
|
|
836
|
+
**Phase 4: Testing**
|
|
837
|
+
- All tests must pass
|
|
838
|
+
- Failing tests block commit
|
|
839
|
+
|
|
840
|
+
**Phase 5: Coverage**
|
|
841
|
+
- Coverage must be ≥80%
|
|
842
|
+
- Blocks commit if below threshold
|
|
843
|
+
|
|
844
|
+
### Code Review Checklist
|
|
845
|
+
|
|
846
|
+
- [ ] New code has tests (TDD followed)
|
|
847
|
+
- [ ] Tests follow AAA pattern
|
|
848
|
+
- [ ] Test names are descriptive
|
|
849
|
+
- [ ] Coverage ≥80%
|
|
850
|
+
- [ ] No flaky tests
|
|
851
|
+
- [ ] Tests are fast (<1s unit, <10s integration)
|
|
852
|
+
- [ ] Mocks used appropriately
|
|
853
|
+
- [ ] Tests are independent
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
|
|
857
|
+
## References
|
|
858
|
+
|
|
859
|
+
- Test Driven Development by Kent Beck
|
|
860
|
+
- Growing Object-Oriented Software, Guided by Tests by Steve Freeman
|
|
861
|
+
- xUnit Test Patterns by Gerard Meszaros
|
|
862
|
+
|
|
863
|
+
---
|
|
864
|
+
|
|
865
|
+
## 📝 WHEN TO UPDATE THIS DOCUMENT
|
|
866
|
+
|
|
867
|
+
This **standards document** updates infrequently - only when testing standards evolve.
|
|
868
|
+
|
|
869
|
+
### When to Update ⚠️
|
|
870
|
+
|
|
871
|
+
Update **only when standards change**:
|
|
872
|
+
|
|
873
|
+
- ✅ **New Test Pattern**: Team discovers better testing approach (mocking, stubbing, etc.)
|
|
874
|
+
- ✅ **Coverage Target Changed**: Team adjusts coverage thresholds based on project needs
|
|
875
|
+
- ✅ **Framework Upgrade**: Test framework version changes testing best practices
|
|
876
|
+
- ✅ **Test Strategy Gap**: Issue missed by tests reveals gap in testing principles
|
|
877
|
+
|
|
878
|
+
### How to Update
|
|
879
|
+
|
|
880
|
+
1. Add new test pattern example to relevant section
|
|
881
|
+
2. Update coverage targets if team consensus changed
|
|
882
|
+
3. Add testing strategy for new component types
|
|
883
|
+
4. Cross-reference to ISSUES.md if pattern prevents test gaps
|
|
884
|
+
5. Update timestamp: `Last reviewed: {{date}}`
|
|
885
|
+
|
|
886
|
+
**Cross-References**: When updating, check [ISSUES.md](./ISSUES.md) - does new standard prevent bugs that tests missed?
|
|
887
|
+
|
|
888
|
+
**Update Frequency**: Rare (quarterly or less) - testing standards are stable
|
|
889
|
+
|
|
890
|
+
---
|
|
891
|
+
|
|
892
|
+
**Document maintained by**: ZEN (Documentation Specialist)
|
|
893
|
+
**Enforced by**: KIL (Task Executor - TDD), BAS (Quality Fixer - Coverage), APO (Acceptance Test Generator)
|
|
894
|
+
**Last reviewed**: {{date}}
|