Testing Basics: Unit Tests & TDD
Testing ensures your code works correctly and keeps working as you add features. Unit tests verify individual functions. Test-Driven Development (TDD) writes tests BEFORE code. Testing is expected in professional development.
Testing Essentials
- Unit test — Tests a single function in isolation. Input → Expected output
- Integration test — Tests multiple components working together
- E2E test — Tests the full user flow (click button → see result)
- Jest / Vitest — Popular JS testing frameworks. describe, test, expect
- TDD — Write test first (fails), then write code to make it pass, then refactor
- expect(value).toBe(expected) — Assertion. Tests pass when expectations are met
- Coverage — What % of code is tested. Aim for 70-80% on critical paths
Testing Code
// Functions to test
function add(a, b) {
return a + b;
}
function isPrime(n) {
if (n <= 1) return false;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (n % i === 0) return false;
}
return true;
}
function formatCurrency(amount, currency = "INR") {
if (typeof amount !== "number") throw new TypeError("Amount must be a number");
if (amount < 0) throw new RangeError("Amount cannot be negative");
return currency === "INR" ? `₹${amount.toFixed(2)}` : `$${amount.toFixed(2)}`;
}
// Unit tests (Jest/Vitest syntax)
// describe("add", () => {
// test("adds two positive numbers", () => {
// expect(add(2, 3)).toBe(5);
// });
// test("handles negative numbers", () => {
// expect(add(-1, 1)).toBe(0);
// });
// test("handles zero", () => {
// expect(add(0, 0)).toBe(0);
// });
// });
// describe("isPrime", () => {
// test("returns false for 0 and 1", () => {
// expect(isPrime(0)).toBe(false);
// expect(isPrime(1)).toBe(false);
// });
// test("identifies prime numbers", () => {
// expect(isPrime(2)).toBe(true);
// expect(isPrime(7)).toBe(true);
// expect(isPrime(13)).toBe(true);
// });
// test("identifies non-prime numbers", () => {
// expect(isPrime(4)).toBe(false);
// expect(isPrime(9)).toBe(false);
// });
// });
// Manual testing simulation
console.log("Testing add:");
console.assert(add(2, 3) === 5, "2 + 3 should equal 5");
console.assert(add(-1, 1) === 0, "-1 + 1 should equal 0");
console.log("✅ add tests passed");
console.log("\nTesting isPrime:");
console.assert(isPrime(7) === true, "7 should be prime");
console.assert(isPrime(4) === false, "4 should not be prime");
console.log("✅ isPrime tests passed");Tip
Tip
Write tests for the behavior you want, not the implementation. Test 'calculateTotal returns correct sum' not 'calculateTotal calls reduce'. Implementation can change; behavior should stay the same.
Use console.table for objects. console.time to profile. debugger; for breakpoints. Chrome DevTools > console.log.
Common Mistake
Warning
Testing implementation details instead of outcomes. Don't test that a function calls another function internally. Test that given specific inputs, you get the expected outputs. This makes tests resilient to refactoring.
Practice Task
Note
Testing: (1) Write 3 unit tests for a calculator function using Jest or Vitest. (2) Test edge cases: division by zero, negative numbers, non-numeric input. (3) Run tests and achieve 100% coverage.
Quick Quiz
Key Takeaways
- Testing ensures your code works correctly and keeps working as you add features.
- Unit test — Tests a single function in isolation. Input → Expected output
- Integration test — Tests multiple components working together
- E2E test — Tests the full user flow (click button → see result)