Expert · Functional patterns and testing · topic 50 of 61 · 30 min
Testing your code
Explanation
Why automated tests?
Checking by hand ("I clicked around and it looked fine") does not scale. When you change one function in a 50,000-line app, you cannot re-check everything by hand. An automated test suite re-checks hundreds of behaviours in seconds, every time, and tells you exactly what broke. Every task in this course is checked by small automated tests.
Kinds of tests
| Kind | Checks | Speed | Tools |
|---|---|---|---|
| Unit | One function or class alone | Milliseconds | Vitest, Jest, node:test |
| Integration | Several parts together (API + database) | Seconds | Vitest/Jest + a test database, Supertest |
| End-to-end (E2E) | The real app in a real browser | Seconds to minutes | Playwright, Cypress |
The "testing pyramid" advice: many fast unit tests, fewer integration tests, and a small number of E2E tests for the most important user journeys (sign up, checkout).
A first test with Vitest (Jest looks almost the same)
// price.js
export function applyDiscount(price, percent) {
if (percent < 0 || percent > 100) throw new RangeError('Bad percent');
return Math.round(price * (1 - percent / 100));
}
// price.test.js
import { describe, it, expect } from 'vitest';
import { applyDiscount } from './price.js';
describe('applyDiscount', () => {
it('takes the percentage off', () => {
expect(applyDiscount(1000, 10)).toBe(900);
});
it('rounds to the nearest rupee', () => {
expect(applyDiscount(999, 15)).toBe(849);
});
it('rejects impossible percentages', () => {
expect(() => applyDiscount(100, 120)).toThrow(RangeError);
});
});npm install --save-dev vitest
npx vitest runArrange, act, assert
A clear test has three parts: arrange (set up the inputs), act (call the code), assert (check the result). One behaviour per test, with a name that reads like a sentence: "rejects impossible percentages". When it fails, the name tells you what broke.
What to test: edge cases
- The normal case (₹1000 with 10%).
- Boundaries: 0, 100, exactly at a limit (₹499 for free delivery).
- Empty inputs: [], '', no arguments.
- Bad inputs: negative numbers, wrong types, null.
- Rounding and money: 0.1 + 0.2 style surprises.
node:test, built into Node
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { applyDiscount } from './price.js';
test('takes 10% off', () => {
assert.equal(applyDiscount(1000, 10), 900);
});
// run with: node --testTDD and coverage
Test-driven development (TDD) means writing a failing test first, then the simplest code that passes, then cleaning up (red, green, refactor). Code coverage tools report which lines your tests ran. High coverage is a good sign but not proof: a test can run a line without checking that its result is right.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Testing your code · 1/6
In this lesson you will write automated tests that check your code for you.
Prefer a video by a teacher? Find free videos on “javascript unit testing vitest jest tutorial for beginners” on YouTube ↗
In real life: Testing GST rules before a rate change
Before a government rate change, an e-commerce company's finance team updated their tax function. The test suite of 120 cases (different categories, interstate vs intrastate, exempt items, rounding) ran in two seconds and found that the new code mis-rounded one category. It was fixed before any invoice went out wrong.
function assertEqual(actual, expected, msg) { if (actual !== expected) throw new Error(`${msg}: expected ${expected}, got ${actual}`); }
const applyDiscount = (price, pct) => Math.round(price * (1 - pct / 100));
const cases = [[1000, 10, 900], [999, 15, 849], [500, 0, 500], [500, 100, 0]];
for (const [price, pct, want] of cases) assertEqual(applyDiscount(price, pct), want, `${price} - ${pct}%`);
console.log(`${cases.length} tests passed`);Practice (0/3 done)
Write your own tiny test helper expectEqual(actual, expected, name) that returns "PASS <name>" when actual === expected and "FAIL <name>: expected <expected>, got <actual>" otherwise.
freeDelivery(total) should return true when total is at least 499. Write the function AND an array cases of at least 4 [input, expected] pairs that include the boundary values 498, 499 and 500. The test runs your cases against your function and a buggy version; your cases must catch the bug.
On your computer: create a folder, run npm init -y and npm install --save-dev vitest, write sum.js exporting sum(list) and sum.test.js with at least 3 tests (normal, empty list, negative numbers), and run npx vitest run. Then break sum on purpose and see a test fail.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Test doubles and testable designFunctional patterns and testing · same module
- Composition, currying and pipelinesFunctional patterns and testing · same module
- Testing basics: unit, integration and end-to-endFrontend Web Development · Fast, accessible, findable and safe: quality for real sites
- Unit testing: checking one piece at a timeBackend Development · Testing and API documentation
- Unit testing with xUnitC# and .NET · Testing, tooling and Unity
- Integration testing: checking parts work togetherBackend Development · Testing and API documentation
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which test kind should you have the MOST of?
Answer: Unit
Unit tests are fast and pinpoint failures; the pyramid has them at the base.
What are the three parts of a clear test?
Answer: Arrange, act, assert
Set up, call the code, check the result.
What does 100% code coverage prove?
Answer: Every line was run by some test
Coverage shows lines executed, not that results were checked correctly.
In TDD, what do you write first?
Answer: A failing test
Red (failing test), then green (code), then refactor.
What does Arrange-Act-Assert mean?Related
Answer: Set up the data, call the code, check the result
It is the simple shape of almost every test.
In Arrange, Act, Assert, what happens in the Act step?Related
Answer: You call the code being tested
Arrange prepares, Act calls the function once, Assert checks the result.
What does Arrange-Act-Assert describe?Related
Answer: The structure of a test: set up, call, check
It keeps tests readable.
What does an integration test check that a unit test does not?Related
Answer: That several parts, like handler and database, work together
Integration tests check the joins between parts, where many real bugs live.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Functional patterns and testing” before its exam: Test doubles and testable design.
- Module 10 exam: Functional patterns and testing · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Performance and Node.js. 61 topics left in the course before the final exam.