Testing JavaScript
Testing JavaScript
The Testing Pyramid, AAA pattern, test runners, mocks, and behavior-driven verification.
The Automotive Safety Crash Test Facility
Automated testing ensures code correctness, prevents regressions, and enables fearless refactoring across complex codebases.
The Testing Pyramid
Unit Tests (fast, isolated, plentiful) at the base β Integration Tests (verifying connected modules) in the middle β End-to-End Tests (real browser flows, slowest, highest fidelity) at the top.
The AAA Pattern (Arrange, Act, Assert)
Structure every test cleanly: 1. Arrange (set up inputs and mocks), 2. Act (execute the target function), 3. Assert (verify results match expectations).
Mocks, Spies, and Stubs
Isolate units from external systems (databases, timers, HTTP calls) by replacing real dependencies with controllable test doubles.
Test-Driven Development (TDD)
The Red-Green-Refactor cycle: write a failing test first (Red) β write minimal code to pass (Green) β refactor with confidence.
Writing an isolated unit test following the AAA pattern with custom assertions:
// Target function to test
function calculateDiscount(price, isVip) {
if (price < 0) throw new RangeError("Price cannot be negative");
const discountRate = isVip ? 0.2 : 0.05;
return price - (price * discountRate);
}
// Mini Test Runner Simulation
function test(name, fn) {
try {
fn();
console.log(`β
PASS: ${name}`);
} catch (err) {
console.error(`β FAIL: ${name}\n ${err.message}`);
}
}
function expect(actual) {
return {
toBe(expected) {
if (actual !== expected) throw new Error(`Expected ${expected}, but got ${actual}`);
},
toThrow(errorType) {
// verified in try/catch block
}
};
}
// Executing Tests
test("calculates 20% discount for VIP customers", () => {
// 1. Arrange
const price = 100;
const isVip = true;
// 2. Act
const finalPrice = calculateDiscount(price, isVip);
// 3. Assert
expect(finalPrice).toBe(80);
});
test("calculates 5% discount for regular customers", () => {
expect(calculateDiscount(100, false)).toBe(95);
});
β
PASS: calculates 20% discount for VIP customers
β
PASS: calculates 5% discount for regular customers
Testing Implementation Details Instead of Behavior
The Risk: Writing tests that assert internal private variable names or exact loop counters makes tests brittleβthey break on harmless refactors even when functionality is intact.
The Fix: Test observable behavior: test inputs vs outputs from the consumer's perspective.
- β Testing Pyramid: Many fast Unit tests, moderate Integration tests, few E2E tests.
- β Follow the AAA pattern: Arrange β Act β Assert.
- β Test behavior and contracts, not internal implementation details.
- β Mock external dependencies (HTTP, timers, DB) to keep tests deterministic and fast.
- β A good test suite gives confidence to refactor without fear of regressions.