What you'll be able to do
By the end of this page you should be able to write small tests for pure functions, use the arrange/act/assert shape, and know what a test runner adds later — without drowning in config.
Previous post: security basics for frontend.
Who this is for
- People who break old features when adding new ones
- Beginners curious what "unit test" means for a calculator-style function
- Anyone intimidated by Jest config before writing one assert
You can skip this if you already write tests daily. This closes Phase 11 with the habit, not a full CI course.
Why test a function
A test is a tiny program that calls your function with known inputs and checks outputs.
Scenario A — add(2, 3) must stay 5 after a refactor.
Scenario B — discount rules change; tests list the cases you refuse to forget.
Arrange, act, assert
function add(a, b) {
return a + b;
}
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(label + ": expected " + expected + " got " + actual);
}
}
// arrange
const a = 2;
const b = 3;
// act
const result = add(a, b);
// assert
assertEqual(result, 5, "add 2+3");
console.log("ok");
Scenario A — pure helpers are the easiest first tests.
Scenario B — DOM-heavy code needs more setup; start with pure logic extracted from the UI.
What a runner gives you later
Tools like Node's test runner or Jest watch files, report many cases, and integrate with CI. The idea is the same: call code, compare results, fail loudly.
Scenario A — learning. Hand asserts in a file are enough to feel the habit.
Scenario B — team project. Adopt the repo's runner; do not invent a second one.
A tiny practice file
<!DOCTYPE html>
<html>
<body>
<pre id="out"></pre>
<script>
function clamp(n, min, max) {
if (n < min) return min;
if (n > max) return max;
return n;
}
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(label + ": expected " + expected + " got " + actual);
}
}
try {
assertEqual(clamp(5, 0, 10), 5, "mid");
assertEqual(clamp(-1, 0, 10), 0, "low");
assertEqual(clamp(99, 0, 10), 10, "high");
document.getElementById("out").textContent = "all tests passed";
} catch (err) {
document.getElementById("out").textContent = String(err.message);
}
</script>
</body>
</html>
Break clamp on purpose and watch the message.
Mistakes I see a lot
1. Testing implementation details ("was this private helper called?") instead of results.
2. One giant test that asserts ten unrelated things — hard to diagnose.
3. No failing test first when fixing a bug — add the case, see red, then fix.
4. Skipping tests for "simple" money/date logic — that is where silent bugs hide.
What to try before the next post
- Write three asserts for a function you already have.
- Fix a bug by adding a failing assert first.
- Skim Node's test docs when you bridge to Node later — same habit, better runner.
This closes Phase 11 — engineering habits. Next we begin Phase 12 — practice: coding problems warmup.
Try this next outside the series
Engineering habits only stick when they show up in testing, security, and maintenance work.
- React Testing Library basics — turn clean habits into repeatable checks instead of guesses
- Node.js security basics — connect beginner discipline to safer real-world code