Unit testing is the practice of checking whether small, individual pieces of your code (usually functions) work the way you expect. Instead of waiting for bugs to appear later, tests help you catch mistakes early and make changes confidently.
Vitest is a fast, modern testing framework built for JavaScript. It works similarly to Jest and integrates well with modern tooling.
Testing is not about proving your code is perfect — it’s about proving your assumptions are correct.
A unit test:
If something is wrong, the test tells you exactly where the problem is.
import { test, expect } from "vitest";
import { sum } from "./sum.js";
test("adds two numbers", () => {
expect(sum(2, 3)).toBe(5);
});
This test checks that sum(2, 3) returns 5. If the function is broken, Vitest will tell you.
💡 Tests give you confidence when changing code, refactoring, or adding new features.
Testing helps you:
Without tests, every change to the code is a gamble.
⚠️ Debugging without tests is like fixing a plane while flying it — risky and stressful.
Install Vitest:
npm install --save-dev vitest
Add this to your package.json:
{
"scripts": {
"test": "vitest"
}
}
Run tests:
npm test
💡 Vitest automatically finds all files ending with .test.js or .spec.js.
Almost every unit test follows this structure.
Set up data, inputs, or environment.
Run the function you are testing.
Check that the result matches your expectation.
Example:
import { test, expect } from "vitest";
import { multiply } from "./math.js";
test("multiplies two numbers", () => {
// Arrange
const a = 4;
const b = 5;
// Act
const result = multiply(a, b);
// Assert
expect(result).toBe(20);
});
This pattern makes your tests clean, readable, and easy to understand.
Imagine this function:
export function findMax(numbers) {
let max = numbers[0];
for (const n of numbers) {
if (n > max) max = n;
}
return max;
}
We can test it like this:
import { test, expect } from "vitest";
import { findMax } from "./findMax.js";
test("finds the largest number", () => {
expect(findMax([1, 5, 3])).toBe(5);
});
test("works with negative numbers", () => {
expect(findMax([-10, -3, -20])).toBe(-3);
});
💡 Good tests include normal cases, edge cases, and weird cases.
Some situations don’t appear often but can break your code.
Examples:
test("handles a single-item array", () => {
expect(findMax([7])).toBe(7);
});
test("handles an empty array", () => {
expect(findMax([])).toBe(undefined); // or decide your behavior
});
You decide what your function should do — tests make that decision explicit.
expect methods)Vitest provides many powerful assertions:
Simple values:
expect(result).toBe(10);
expect(name).toBe("Alice");
Object comparison:
expect(user).toEqual({ name: "Sam", age: 20 });
Truthiness:
expect(isLoggedIn).toBeTruthy();
expect(errors.length).toBeFalsy();
Arrays:
expect(colors).toContain("blue");
Error checking:
expect(() => divide(10, 0)).toThrow();
💡 toBe compares primitive values. toEqual compares objects or arrays.
You can test the same function with different data:
test("returns true for even numbers", () => {
expect(isEven(4)).toBe(true);
expect(isEven(10)).toBe(true);
});
test("returns false for odd numbers", () => {
expect(isEven(3)).toBe(false);
});
This gives stronger confidence that your function behaves correctly.
A failing test is not a bad sign — it's a useful signal.
Vitest shows:
Use failures to guide your debugging.
💡 A failing test is just your program telling you the truth.
Good structure:
src/
math.js
findMax.js
tests/
math.test.js
findMax.test.js
But placing test files next to code also works:
math.js
math.test.js
Choose whichever keeps your project clean.
Some developers write tests before the code.
Why?