Unit Testing in JavaScript / TypeScript
Unit Testing in JavaScript / TypeScript
Section titled “Unit Testing in JavaScript / TypeScript”Jest and Vitest are the dominant testing frameworks for JavaScript and TypeScript. Vitest is faster and integrates natively with Vite projects.
Jest Setup
Section titled “Jest Setup”npm install -D jest @types/jest ts-jestjest.config.ts:
export default { preset: 'ts-jest', testEnvironment: 'node', testMatch: ['**/*.test.ts', '**/*.spec.ts'],};Vitest Setup (for Vite/React projects)
Section titled “Vitest Setup (for Vite/React projects)”npm install -D vitest @vitest/ui jsdomvite.config.ts:
import { defineConfig } from 'vite';export default defineConfig({ test: { environment: 'jsdom', globals: true, },});Writing Tests
Section titled “Writing Tests”export function add(a: number, b: number): number { return a + b;}
export function divide(a: number, b: number): number { if (b === 0) throw new Error('Division by zero'); return a / b;}import { add, divide } from './calculator';
describe('Calculator', () => { describe('add', () => { it('returns the sum of two numbers', () => { expect(add(2, 3)).toBe(5); });
it.each([ [0, 5, 5], [-1, 1, 0], [100, -50, 50], ])('add(%i, %i) = %i', (a, b, expected) => { expect(add(a, b)).toBe(expected); }); });
describe('divide', () => { it('returns the quotient', () => { expect(divide(10, 2)).toBe(5); });
it('throws when dividing by zero', () => { expect(() => divide(10, 0)).toThrow('Division by zero'); }); });});Common Matchers
Section titled “Common Matchers”expect(value).toBe(5); // strict equality (===)expect(value).toEqual({ a: 1 }); // deep equalityexpect(value).toBeTruthy();expect(value).toBeFalsy();expect(value).toBeNull();expect(value).toBeUndefined();expect(value).toBeGreaterThan(0);expect(arr).toHaveLength(3);expect(arr).toContain('apple');expect(obj).toHaveProperty('name', 'Alice');expect(str).toMatch(/regex/);expect(fn).toThrow('error message');Mocking
Section titled “Mocking”import { fetchUser } from './api';import { getUserProfile } from './userService';
jest.mock('./api'); // auto-mock the entire module
it('returns profile with user data', async () => { // Set up the mock (fetchUser as jest.Mock).mockResolvedValueOnce({ id: 1, name: 'Alice', });
const profile = await getUserProfile(1);
expect(profile.name).toBe('Alice'); expect(fetchUser).toHaveBeenCalledWith(1); expect(fetchUser).toHaveBeenCalledTimes(1);});Spy on Functions
Section titled “Spy on Functions”const sendEmail = jest.spyOn(emailService, 'send');sendEmail.mockResolvedValue(undefined);
await userService.register({ email: 'alice@example.com' });
expect(sendEmail).toHaveBeenCalledWith( expect.objectContaining({ to: 'alice@example.com' }));Testing Async Code
Section titled “Testing Async Code”// Async/awaitit('fetches user data', async () => { const user = await userApi.getById(1); expect(user.name).toBe('Alice');});
// Promisesit('resolves with data', () => { return userApi.getById(1).then(user => { expect(user.name).toBe('Alice'); });});
// Rejects / throwsit('rejects when user not found', async () => { await expect(userApi.getById(999)).rejects.toThrow('User not found');});React Component Testing
Section titled “React Component Testing”npm install -D @testing-library/react @testing-library/user-eventimport { render, screen } from '@testing-library/react';import userEvent from '@testing-library/user-event';import { LoginForm } from './LoginForm';
it('calls onSubmit with email and password', async () => { const handleSubmit = jest.fn(); render(<LoginForm onSubmit={handleSubmit} />);
await userEvent.type(screen.getByLabelText(/email/i), 'alice@example.com'); await userEvent.type(screen.getByLabelText(/password/i), 'secret123'); await userEvent.click(screen.getByRole('button', { name: /log in/i }));
expect(handleSubmit).toHaveBeenCalledWith({ email: 'alice@example.com', password: 'secret123', });});Setup and Teardown
Section titled “Setup and Teardown”beforeAll(() => { /* run once before all tests */ });afterAll(() => { /* run once after all tests */ });beforeEach(() => { /* run before each test */ });afterEach(() => { /* run after each test */ });Running Tests
Section titled “Running Tests”npx jest # run all testsnpx jest --watch # re-run on file changesnpx jest --coverage # with coverage reportnpx jest user.test.ts # run specific filenpx jest -t "add" # run tests matching pattern