JavaScript Testing Fundamentals
Learn JavaScript testing with Vitest/Jest: unit tests, integration tests, mocking, and test-driven development.
Why Test?
Tests catch bugs before they reach users, make refactoring safe, and serve as living documentation for your code.
Setting Up Vitest
``bash
npm install -D vitest
`
`javascript
// vitest.config.js
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: { globals: true }
});
`
Writing Your First Test
`javascript
// math.js
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
// math.test.js import { describe, it, expect } from 'vitest'; import { add, multiply } from './math';
describe('Math functions', () => { it('should add two numbers', () => { expect(add(2, 3)).toBe(5); expect(add(-1, 1)).toBe(0); expect(add(0, 0)).toBe(0); });
it('should multiply two numbers', () => {
expect(multiply(3, 4)).toBe(12);
expect(multiply(0, 5)).toBe(0);
});
});
`
Common Matchers
`javascript
// Equality
expect(value).toBe(42); // Strict equality
expect(obj).toEqual({ a: 1 }); // Deep equality
expect(value).toBeNull();
expect(value).toBeDefined();
expect(value).toBeTruthy();
// Numbers expect(value).toBeGreaterThan(3); expect(value).toBeLessThanOrEqual(10); expect(0.1 + 0.2).toBeCloseTo(0.3);
// Strings expect(str).toMatch(/regex/); expect(str).toContain('substring');
// Arrays expect(arr).toContain(item); expect(arr).toHaveLength(3);
// Errors
expect(() => riskyFn()).toThrow();
expect(() => riskyFn()).toThrow('specific message');
`
Mocking
`javascript
import { vi, describe, it, expect } from 'vitest';
// Mock a function const mockFetch = vi.fn(); mockFetch.mockResolvedValue({ json: () => ({ id: 1, name: 'Alice' }) });
// Mock a module vi.mock('./api', () => ({ fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }) }));
// Spy on existing function
const consoleSpy = vi.spyOn(console, 'log');
myFunction();
expect(consoleSpy).toHaveBeenCalledWith('expected output');
`
Testing Async Code
`javascript
it('should fetch user data', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('Alice');
});
it('should reject with error for invalid ID', async () => {
await expect(fetchUser(-1)).rejects.toThrow('Invalid ID');
});
`
Test-Driven Development (TDD)
1. Red: Write a failing test 2. Green: Write minimum code to pass 3. Refactor: Improve code while keeping tests green
`javascript
// Step 1: Write test first
it('should return fizz for multiples of 3', () => {
expect(fizzBuzz(3)).toBe('Fizz');
expect(fizzBuzz(9)).toBe('Fizz');
});
// Step 2: Implement function fizzBuzz(n) { if (n % 15 === 0) return 'FizzBuzz'; if (n % 3 === 0) return 'Fizz'; if (n % 5 === 0) return 'Buzz'; return String(n); } ``
Best Practices
1. Test behavior, not implementation details 2. Keep tests independent — each test should run in isolation 3. Use descriptive test names that explain what and why 4. Follow AAA pattern: Arrange, Act, Assert 5. Don't mock everything — only external dependencies 6. Aim for meaningful coverage, not 100%