--- name: testing description: Comprehensive testing standards including unit, integration, security, and property-based testing with TDD methodology --- # Testing Standards Skill ## Level 1: Quick Start (5 minutes) ### What You'll Learn Implement effective testing strategies to ensure code quality, catch bugs early, and maintain confidence in changes. ### Core Principles - **Test First**: Write tests before implementation (TDD) - **Coverage**: Aim for >80% code coverage - **Isolation**: Tests should be independent and repeatable - **Speed**: Keep unit tests fast (<100ms each) ### Testing Pyramid ``` /\ / \ E2E Tests (Few) /----\ / Inte \ / gration \ (Some) / Tests \ /------------\ / Unit Tests \ (Many) /--------------\ ``` ### Quick Start Example ```typescript // 1. Write the test first (RED) describe("calculateDiscount", () => { it("should apply 10% discount for premium users", () => { const user = { tier: "premium" }; const order = { total: 100 }; const result = calculateDiscount(user, order); expect(result).toBe(90); }); }); // 2. Implement minimal code (GREEN) function calculateDiscount(user, order) { if (user.tier === "premium") { return order.total * 0.9; } return order.total; } // 3. Refactor (REFACTOR) function calculateDiscount( user: User, order: Order ): number { const PREMIUM_DISCOUNT = 0.1; return user.tier === "premium" ? order.total * (1 - PREMIUM_DISCOUNT) : order.total; } ``` ### Essential Checklist - [ ] Unit tests for all business logic - [ ] Integration tests for critical paths - [ ] Tests run in CI/CD pipeline - [ ] Coverage >80% - [ ] Tests are maintainable and readable ### Common Pitfalls - Testing implementation details instead of behavior - Slow tests that discourage running them - Flaky tests that fail intermittently - Poor test isolation (shared state) --- ## Level 2: Implementation (30 minutes) ### Deep Dive Topics #### 1. Test-Driven Development (TDD) **Red-Green-Refactor Cycle:** ```typescript // STEP 1: RED - Write failing test describe("UserService", () => { describe("createUser", () => { it("should hash password before saving", async () => { const userData = { email: "test@example.com", password: "SecurePass123!", }; const service = new UserService(mockRepository, mockHasher); await service.createUser(userData); expect(mockHasher.hash).toHaveBeenCalledWith("SecurePass123!"); expect(mockRepository.save).toHaveBeenCalledWith( expect.objectContaining({ email: "test@example.com", passwordHash: expect.any(String), }) ); }); }); }); // STEP 2: GREEN - Minimal implementation class UserService { constructor( private repository: UserRepository, private hasher: PasswordHasher ) {} async createUser(userData: UserData): Promise { const passwordHash = await this.hasher.hash(userData.password); return this.repository.save({ ...userData, passwordHash, }); } } // STEP 3: REFACTOR - Improve design class UserService { constructor( private repository: UserRepository, private hasher: PasswordHasher, private validator: UserValidator ) {} async createUser(userData: UserData): Promise { await this.validator.validate(userData); const passwordHash = await this.hasher.hash(userData.password); const user = new User({ email: userData.email, passwordHash, createdAt: new Date(), }); return this.repository.save(user); } } ``` **NIST Mapping:** - @nist-controls: [si-10, si-11, au-2, au-3] #### 2. Property-Based Testing ```typescript import { fc } from "fast-check"; // Traditional example-based test it("should reverse a reversed string back to original", () => { expect(reverse(reverse("hello"))).toBe("hello"); expect(reverse(reverse("world"))).toBe("world"); // Limited to specific examples }); // Property-based test - tests thousands of cases it("reversing twice should return original string", () => { fc.assert( fc.property(fc.string(), (str) => { expect(reverse(reverse(str))).toBe(str); }) ); }); // Complex property testing describe("calculateTotal", () => { it("should always return non-negative totals", () => { fc.assert( fc.property( fc.array(fc.record({ price: fc.nat(), quantity: fc.nat() })), (items) => { const total = calculateTotal(items); expect(total).toBeGreaterThanOrEqual(0); } ) ); }); it("should be commutative (order doesn't matter)", () => { fc.assert( fc.property(fc.array(fc.record({ price: fc.nat(), quantity: fc.nat() })), (items) => { const total1 = calculateTotal(items); const total2 = calculateTotal([...items].reverse()); expect(total1).toBe(total2); }) ); }); }); ``` #### 3. Integration Testing ```typescript // API Integration Test describe("POST /api/users", () => { let app: Express; let db: Database; beforeAll(async () => { // Setup test database db = await createTestDatabase(); app = createApp({ database: db }); }); afterAll(async () => { await db.close(); }); beforeEach(async () => { // Clean state between tests await db.truncate("users"); }); it("should create a new user", async () => { const response = await request(app) .post("/api/users") .send({ email: "test@example.com", password: "SecurePass123!", name: "Test User", }) .expect(201); expect(response.body).toMatchObject({ id: expect.any(String), email: "test@example.com", name: "Test User", }); // Verify in database const user = await db.query("SELECT * FROM users WHERE email = ?", [ "test@example.com", ]); expect(user).toBeDefined(); expect(user.passwordHash).not.toBe("SecurePass123!"); // Should be hashed }); it("should reject duplicate emails", async () => { // Create first user await request(app).post("/api/users").send({ email: "test@example.com", password: "SecurePass123!", }); // Attempt duplicate await request(app) .post("/api/users") .send({ email: "test@example.com", password: "AnotherPass456!", }) .expect(409); }); }); ``` #### 4. Security Testing ```typescript // Input validation tests describe("Security: Input Validation", () => { it("should reject SQL injection attempts", async () => { const maliciousInput = "'; DROP TABLE users; --"; await request(app) .get(`/api/users/search?name=${maliciousInput}`) .expect(400); }); it("should sanitize XSS attempts", async () => { const xssPayload = ''; const response = await request(app) .post("/api/comments") .send({ content: xssPayload }) .expect(201); expect(response.body.content).not.toContain("