Module 9: Tooling, Testing, Debugging, and Security
Testing JavaScript Functions
Write small tests that cover expected values, edge cases, and failures.
Tests make behaviour explicit and help detect regressions. A unit test checks a focused unit, often a pure function. Integration tests check that parts work together; end-to-end tests exercise user-visible flows. Use the test runner adopted by your project rather than adding multiple testing tools without a need.
Consider this function:
export function calculateTotal(price, quantity) {
if (!Number.isFinite(price) || price < 0) throw new TypeError("Invalid price");
if (!Number.isInteger(quantity) || quantity < 0) throw new TypeError("Invalid quantity");
return price * quantity;
}
A good test set includes a normal example, boundary values, and invalid input:
console.assert(calculateTotal(10, 3) === 30);
console.assert(calculateTotal(0, 4) === 0);
console.assert is only a quick demonstration, not a substitute for a real test runner in a larger project. Tools such as Vitest, Jest, and Node's built-in test runner support structured assertions and automated reporting.
Test behaviour, not implementation details#
Tests should express the public contract. Avoid making them depend on private local variable names or exact internal steps unless those details are part of the requirement.
Practice#
Write tests for an array utility: empty array, one item, repeated values, and invalid input. Explain which test would catch a bug you might otherwise miss.