Testing Fundamentals
Writing Your First Tests with Vitest
Get started with Vitest — the fast, modern test runner built for TypeScript and Vite-based projects.
Why Vitest
Vitest is the test runner of choice for modern TypeScript and Vite-based projects:
- Fast — Vite-powered, runs tests in parallel, reruns only changed tests in watch mode
- TypeScript-first — No configuration needed for TypeScript
- Jest-compatible API — describe, it, expect work exactly like Jest
- Native ESM support — No CommonJS transformation headaches
Installation and Setup
bash
npm install -D vitestConfigure in vitest.config.ts:
typescript
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true, // Use describe/it without importing
environment: 'node', // or 'jsdom' for browser-like testing
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
},
},
});Add scripts to package.json:
json
{
"scripts": {
"test": "vitest",
"test:watch": "vitest --watch",
"test:coverage": "vitest --coverage"
}
}Test File Naming
Vitest discovers tests in files matching these patterns:
**/*.test.ts**/*.spec.ts- Files inside
**/__tests__/**
Keep test files next to the source files they test:
text
src/
utils/
formatPrice.ts
formatPrice.test.ts ← test file next to source
components/
Button.tsx
Button.test.tsxBasic Test Structure
typescript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { formatPrice, slugify, isValidEmail } from './utils';
// describe() groups related tests
describe('formatPrice', () => {
// it() or test() defines an individual test
it('formats cents as dollar amount', () => {
// expect().toBe() is the most common assertion
expect(formatPrice(1999)).toBe('$19.99');
});
it('formats zero correctly', () => {
expect(formatPrice(0)).toBe('$0.00');
});
});
describe('isValidEmail', () => {
it('returns true for valid emails', () => {
expect(isValidEmail('user@example.com')).toBe(true);
});
it('returns false for missing @ symbol', () => {
expect(isValidEmail('notanemail')).toBe(false);
});
});Essential Assertions
typescript
// Equality
expect(2 + 2).toBe(4); // Exact equality (===)
expect({ a: 1 }).toEqual({ a: 1 }); // Deep equality
expect([1, 2, 3]).toContain(2); // Array contains value
expect('hello world').toMatch(/world/); // String matches regex
// Truthiness
expect(true).toBeTruthy();
expect(null).toBeFalsy();
expect(undefined).toBeUndefined();
expect(null).toBeNull();
// Numbers
expect(10).toBeGreaterThan(5);
expect(3.14).toBeCloseTo(3.14159, 2); // Float comparison
// Errors
expect(() => divide(1, 0)).toThrow('Cannot divide by zero');
// Async
await expect(fetchUser(123)).resolves.toEqual({ id: 123, name: 'Alice' });
await expect(fetchUser(-1)).rejects.toThrow('User not found');Setup and Teardown
typescript
describe('UserService', () => {
let db: TestDatabase;
beforeAll(async () => {
// Runs once before all tests in this describe block
db = await createTestDatabase();
});
afterAll(async () => {
// Runs once after all tests
await db.destroy();
});
beforeEach(async () => {
// Runs before each test — reset to clean state
await db.seed({ users: [{ id: 1, email: 'alice@example.com' }] });
});
afterEach(async () => {
// Runs after each test — clean up
await db.truncate('users');
});
it('finds a user by email', async () => {
const user = await UserService.findByEmail('alice@example.com');
expect(user.id).toBe(1);
});
});Running Tests
bash
npx vitest # Run all tests once
npx vitest --watch # Watch mode — rerun on changes
npx vitest --coverage # Run with coverage report
npx vitest utils # Run tests in files matching "utils"Key Takeaways
- Vitest is the recommended test runner for TypeScript/Vite projects — fast, TypeScript-native, Jest-compatible API
- Test files are co-located with source files using .test.ts or .spec.ts naming
describe()groups tests,it()defines individual tests,expect()makes assertionsbeforeEach/afterEachensure each test starts with clean, predictable state — never rely on test execution order- Watch mode (
vitest --watch) provides instant feedback during development
Example
typescript
// src/utils/formatPrice.test.ts
import { describe, it, expect } from 'vitest';
import { formatPrice } from './formatPrice';
describe('formatPrice', () => {
it('formats positive amounts correctly', () => {
expect(formatPrice(1999)).toBe('$19.99');
expect(formatPrice(100)).toBe('$1.00');
expect(formatPrice(50)).toBe('$0.50');
});
it('handles zero', () => {
expect(formatPrice(0)).toBe('$0.00');
});
it('formats large amounts with commas', () => {
expect(formatPrice(100000)).toBe('$1,000.00');
});
it('throws for negative amounts', () => {
expect(() => formatPrice(-1)).toThrow('Amount cannot be negative');
});
});Try it yourself — TYPESCRIPT