- name
- testing-strategy
- description
- Vitest + Cypress + RTL — deterministic tests, ≥80% line / ≥70% branch coverage, ≥95% on security code, Three.js component testing
- license
- MIT
# Testing Strategy Skill
## Context
Applies when writing unit tests, E2E tests, testing Three.js components, mocking dependencies, measuring coverage, diagnosing flakes, or preparing test data.
Aligned with [Secure Development Policy §Unit Test Coverage & Quality](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Secure_Development_Policy.md).
## Rules
1. **≥ 80 % line / ≥ 70 % branch coverage** across every module
2. **≥ 95 % coverage** on security-sensitive code (input validation, encoding, auth/authorization)
3. **Test pyramid** — many unit, fewer integration, fewest E2E
4. **Behavior, not implementation** — query by role/text; avoid internal-state checks
5. **AAA pattern** — Arrange → Act → Assert; one behavior per test
6. **Mock external dependencies** — APIs, timers, Three.js renderer — with proper types
7. **Cover error and edge cases** — not just the happy path
8. **React Testing Library** — prefer role/text queries; stable `data-testid` allowed on intent
9. **Test logic, not WebGL** — for Three.js, test state/logic; mock the renderer
10. **Isolate tests** — no shared mutable state; `vi.clearAllMocks()` in `beforeEach`
11. **Descriptive names** — "should \<behavior\> when \<condition\>"
12. **Deterministic** — mock `Date.now`, `Math.random`, timers; no real network
13. **Fast** — unit tests in ms, E2E in seconds; parallelize where safe
14. **No flaky tests** — fix the root cause, never retry blindly
15. **No production / PII data in tests** — anonymize or synthesize (per SDP §Test Data Protection)
16. **CI must pass** — no merging red
## Examples
### ✅ Component test with RTL
```tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { HUD } from './HUD';
const mockGameState = {
score: 42, isPlaying: true, timeLeft: 30, combo: 0,
highScore: 100, targetSize: 0.5, level: 1, isNewHighScore: false,
targets: [] as unknown[], totalClicks: 10, successfulHits: 5,
} as const;
describe('HUD', () => {
beforeEach(() => { vi.clearAllMocks(); });
it('should display the current score', () => {
render(<HUD gameState={mockGameState} />);
expect(screen.getByText(/42/)).toBeInTheDocument();
});
});
```
### ✅ Pure-function (no Three.js) test
```typescript
import { describe, it, expect } from 'vitest';
import { calculateLevel, getTargetCountForLevel } from './gameConfig';
describe('gameConfig', () => {
it('should return level 1 for score 0', () => {
expect(calculateLevel(0)).toBe(1);
});
it('should return 1 target for levels 1-3', () => {
expect(getTargetCountForLevel(1)).toBe(1);
expect(getTargetCountForLevel(3)).toBe(1);
});
});
```
### ✅ Mocking Three.js
```tsx
import type { ReactNode } from 'react';
import { vi } from 'vitest';
vi.mock('@react-three/fiber', () => ({
Canvas: ({ children }: { children: ReactNode }) => <div>{children}</div>,
useFrame: vi.fn(),
useThree: () => ({ camera: {}, scene: {}, gl: {} }),
}));
vi.mock('@react-three/drei', () => ({
Sparkles: ({ children }: { children?: ReactNode }) => <>{children}</>,
Trail: ({ children }: { children?: ReactNode }) => <>{children}</>,
}));
```
### ✅ Deterministic RNG
```typescript
import { vi, afterEach } from 'vitest';
beforeEach(() => {
const seq = [0.1, 0.2, 0.3];
let i = 0;
vi.spyOn(Math, 'random').mockImplementation(() => seq[i++ % seq.length]);
});
afterEach(() => { vi.restoreAllMocks(); });
```
### ✅ Security-path test (failure mode)
```typescript
it('should reject non-finite scores', () => {
expect(() => saveHighScore(Number.NaN)).toThrow(RangeError);
expect(() => saveHighScore(Infinity)).toThrow(RangeError);
expect(() => saveHighScore(-1)).toThrow(RangeError);
});
```
### ❌ Anti-Patterns
```typescript
// BAD: testing implementation
expect(component.state.internalValue).toBe(5);
// BAD: non-deterministic
expect(result).toBe(Math.random());
// BAD: vague test name
it('works', () => { /* … */ });
// BAD: shared state between tests
let counter = 0;
it('t1', () => { counter++; });
it('t2', () => { expect(counter).toBe(1); }); // order-dependent!
// BAD: production data
const user = { email: 'real.user@company.com' }; // PII in tests
```
## Flaky-Test Triage
1. Reproduce locally with the same seed/time
2. Classify: timing / network / shared-state / order-dependence
3. Fix at the root (fake timers / explicit mocks / cleanup); never `retry` as a workaround
4. Add a regression test for the fix
## Validation Checklist
- [ ] ≥ 80 % line / ≥ 70 % branch coverage on changed modules
- [ ] ≥ 95 % coverage on security-sensitive code
- [ ] Deterministic (no real clocks, RNG, network)
- [ ] Three.js mocked in unit tests; logic tested in `src/utils/`
- [ ] E2E covers at least one critical user flow for the feature
- [ ] No production / PII data in fixtures
- [ ] CI green locally (`npm run test:ci`, `npm run test:e2e:ci`)
عرض على GitHub