Unit tests are basically a personal assistant who double checks your work every time you change something. Instead of clicking around your app by hand to see if stuff still works, you write lil programs that test your code for you. Automatically. Forever.
In this lesson you'll write unit tests with Vitest, which is one of the most popular testing frameworks for modern JavaScript and TypeScript projects.
What Is a Unit Test?
A unit test checks one small piece of your code, usually a single function. It's answering one question... "Does this function do what I expect it to do?"
Example
// The function we want to test
function add(a: number, b: number): number {
return a + b;
}
// The unit test that checks it
import { describe, it, expect } from 'vitest';
describe('add', () => {
it('should add two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
});
Setting Up Vitest
Vitest plays REALLY nice with modern JavaScript projects. Here's how to set it up.
Example
# Install Vitest as a dev dependency
npm install -D vitest
# Add a test script to package.json
# "scripts": { "test": "vitest" }
Then make a test file. Test files usually end in .test.ts or .spec.ts, which is just the naming habit everybody follows.
Example
my-project/
src/
math.ts # Your code
math.test.ts # Tests for your code
Here is a complete pair you can run. Put the function in src/math.ts.
Example
export function add(a: number, b: number): number {
return a + b;
}
Put the test in src/math.test.ts.
Example
import { describe, it, expect } from 'vitest';
import { add } from './math';
describe('add', () => {
it('adds two numbers', () => {
expect(add(2, 3)).toBe(5);
});
});
Run npx vitest run. Vitest should report one test passed. The shopping-cart example below is a separate illustration. It needs a calculateTotalWithTax function before you can run it.
The Arrange-Act-Assert Pattern
Every good test follows what's called the AAA pattern. Think of it like a play in three acts.
Step 1. Arrange - Set Up the Scene
Create whatever variables, objects or conditions your test needs. This is setting up the stage before the actors walk on.
Step 2. Act - Perform the Action
Call the function, or trigger the behavior you're testing. This is the main event.
Step 3. Assert - Check the Results
Check that the result matches what you expected. This is the moment of truth where you ask "did it actually work?"
Example
describe('ShoppingCart', () => {
it('should calculate total with tax', () => {
// Arrange - set up the data
const items = [
{ name: 'Book', price: 10 },
{ name: 'Pen', price: 2 }
];
const taxRate = 0.1;
// Act - call the function
const total = calculateTotalWithTax(items, taxRate);
// Assert - check the result
// (10 + 2) * 1.1 = 13.2, but computers round decimals a tiny bit
// toBeCloseTo checks it's 13.2 without tripping on 13.200000000000001
expect(total).toBeCloseTo(13.2);
});
});
Common Assertions
Vitest gives you a bunch of ways to check values. These are the ones you'll use most.
Every test should stand on its own two feet. It shouldn't need another test to run first, and it shouldn't leave a mess behind that messes with the next test.
Example
import { describe, it, expect, beforeEach } from 'vitest';
describe('UserService', () => {
let users: string[];
// Runs before each test - fresh start every time
beforeEach(() => {
users = ['Alice', 'Bob'];
});
it('should add a user', () => {
users.push('Charlie');
expect(users).toHaveLength(3);
});
it('should still have only 2 users', () => {
// This passes because beforeEach resets the array!
expect(users).toHaveLength(2);
});
});
Running Your Tests
Once your tests are written, running them is the easy part.
Example
# Run all tests once
npx vitest run
# Run tests in watch mode (re-runs on file changes)
npx vitest
# Run a specific test file
npx vitest run src/math.test.ts
# Run only tests whose name matches a pattern
npx vitest run -t "calculate"
When tests pass you'll see green checkmarks (very satisfying, ngl). When they fail, Vitest shows you exactly what went wrong, including the value it expected and the value it actually got.
TL;DR
Unit tests check one function at a time, all by itself
describe, it, and expect are your building blocks
Arrange-Act-Assert keeps your tests clear and easy to read
Use beforeEach so your tests don't lean on each other
Run your tests often while you build so bugs get caught early
What's Next?
You can write basic tests now. Next up we dig into common testing patterns like mocking, spies, and some tricks for testing the awkward stuff...