A unit test gives one small piece of logic a clear input and checks its observable result. Vitest provides a test runner and assertion library that work well with modern JavaScript projects. The key habit is to test behavior at a stable boundary instead of reproducing the implementation line by line.
Install and add a test command #
Install Vitest in a project that already uses npm:
npm install --save-dev vitest
Add a script to package.json:
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
}
}
vitest run performs one run and exits, which is useful in CI. The watch command reruns related tests while you edit.
Arrange, act, assert #
Extract logic that does not depend on the DOM or a live service into a function that can be tested directly:
export function formatDisplayName(user) {
const first = user.firstName?.trim() ?? "";
const last = user.lastName?.trim() ?? "";
return [first, last].filter(Boolean).join(" ") || "Guest";
}
Then cover the behavior with a focused test:
import { describe, expect, it } from "vitest";
import { formatDisplayName } from "./formatDisplayName.js";
describe("formatDisplayName", () => {
it("joins non-empty name parts", () => {
expect(formatDisplayName({ firstName: " Ada ", lastName: "Lovelace" }))
.toBe("Ada Lovelace");
});
it("uses Guest when no name is available", () => {
expect(formatDisplayName({})).toBe("Guest");
});
});
The setup creates a representative input, the action calls the function once, and the assertion checks what a caller can observe.
Cover meaningful boundaries #
Add cases for empty values, optional fields, invalid input, and boundaries where behavior changes. Avoid writing a test for every possible string if many strings follow the same rule. A small set of representative cases makes the contract easy to read.
Keep tests independent #
Each test should pass regardless of execution order. Create fresh objects in each test, avoid sharing mutable state, and do not rely on real time, random values, or network availability. For code that reads the current time, inject a clock or use Vitest’s fake timers for the narrow behavior that depends on time.
When not to mock #
Pure functions do not need mocks. A mock is useful when a test must control or observe a dependency such as an email sender. Too many mocks can make tests pass while the real components no longer work together; cover those boundaries with integration tests as well.
Next: testing asynchronous fetch code and testing user-visible React behavior.