A discount function behaves well for six months. Then a colleague adds a special case, and the calculation starts mishandling carts above one hundred euros.
A unit test is the line of code that would have reported the regression within the second.
Definition
A unit test checks the behavior of one unit of code, usually a function, cut off from its dependencies: no network, no database, no real clock. It supplies an input, makes the call, and compares the output against what was expected.
import { expect, it } from "vitest";
import { discount } from "./cart.js";
it("takes 10 euros off above 100", () => {
const total = 150; // arrange
const result = discount(total); // act
expect(result).toBe(140); // assert
});Those three steps, arrange, act, assert, are the structure of nearly every unit test. A test that does not let them be seen is almost always a test doing too much at once.
Its place among the other tests
| Kind | What it covers | What it costs |
|---|---|---|
| Unit | One function, isolated | A few milliseconds |
| Integration | Several pieces put together | Hundreds of milliseconds |
| End to end | The full journey inside a browser | Several seconds |
The table holds three columns because there are only three questions: what, at what price, and therefore in what quantity. Unit tests are the most numerous because they are the cheapest.
What makes a function testable
- A returned value rather than an Side effect: whatever a plain return reveals can be tested in one line.
- Dependencies received as arguments rather than hard-imported, which is what allows a Mock to be passed in at test time.
- No hidden data: a Pure function always hands back the same thing for the same inputs, which is exactly what a test knows how to assert.
When a test becomes painful to write, the problem is rarely the test. It is the sign that the function does two things, or that it fetches what it needs on its own.
Frequently asked questions
How many tests for one function?
One per behavior, not one per line. In practice that means the nominal case, the boundaries, and the expected failure. Three tests describing three different situations are worth more than ten repeating the same one with different numbers.
Should internal functions be tested?
No, test what is exported. An internal function is an implementation detail, and testing it freezes that detail: the slightest reorganization then breaks tests although no behavior changed. If an internal function deserves its own tests, it probably deserves its own module.
Write the test before the code?
That is the principle of test-driven development, and it has a merit independent of the dogma: writing the call before the implementation forces the signature to be decided from the caller's point of view. Many teams apply it to calculation functions and skip it elsewhere.