How do you write a unit test?
By FrontendPro Editorial Team Updated 9/26/2026
Answer
A unit test states an expectation about one piece of behaviour and fails loudly when it stops holding.
import { describe, it, expect } from "vitest";
import { priceFor } from "./pricing";
describe("priceFor", () => {
it("applies the member discount", () => {
expect(priceFor(100, { member: true })).toBe(90);
});
it("leaves non-member prices unchanged", () => {
expect(priceFor(100, { member: false })).toBe(100);
});
});
Arrange, act, assert
Set up the inputs, run the thing, check the result. Keeping those three steps visible makes a failing test readable months later.
Name the behaviour, not the function
"applies the member discount" tells you what broke from the failure output alone. "test priceFor" tells you nothing.
One behaviour per test
Several assertions are fine when they describe one outcome. But a test covering three unrelated cases stops at the first failure and hides the other two.
Test the contract, not the implementation
Assert on what the function returns or the effect it has, never on private internals. A test that breaks when you rename a variable is a test that will be deleted rather than fixed.
Cover the edges
The happy path rarely breaks. Write cases for empty input, zero, negative numbers, missing fields, and the errors you expect:
it("rejects a negative price", () => {
expect(() => priceFor(-1, {})).toThrow("Invalid price");
});
Keep tests independent
No shared mutable state between cases and no reliance on execution order — use beforeEach to rebuild fixtures. A suite that only passes when run in one particular order will waste hours later.
Mock only what you must: the network, the clock, randomness. Mocking your own modules heavily usually means the code has too many dependencies, and the test then verifies the mocks rather than the behaviour.
